﻿/*
 created by Wilson Chen (unruledboy@gmail.com) under LGPL v3
 */
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Security.Cryptography;
using System.Diagnostics;
using System.Threading;
using System.Net;
using System.IO.Compression;
using System.Reflection;
using System.Xml.Serialization;
#if (DRIVEID)
using System.Management;
#endif

namespace Xnlab.Filio
{

    #region Fundamental
    internal class RemoteCommander
    {
        internal const char ParameterSeparator = '|';
        internal const string KeyValueSeparator = "=";
        internal const string FileGroupSeparator = "*";
        internal const string FileItemSeparator = "?";
        internal const string CommandPrefix = "cmd";
        internal const string CommandQuery = "query";
        internal const string CommandMatch = "match";
        internal const string CommandAccessDenied = "deny";
        internal const string CommandNoData = "nodata";
        internal const string CommandNoCommand = "nocommand";
        internal const string CommandDiscover = "discover";
        internal const string CommandDelete = "delete";
        internal const string ParameterUserName = "username";
        internal const string ParameterPassword = "password";
        internal const string ParameterFilePaths = "filepaths";
        internal const string ParameterIncludeFileTypes = "includefiletypes";
        internal const string ParameterSearchType = "searchtype";
        internal const string ParameterMatchType = "matchtype";
        internal const string ParameterKeyword = "keyword";
        internal const string ParameterHashMethod = "hashmethod";

        internal static bool ParseCommand(byte[] Content, out Dictionary<string, string> Parameters)
        {
            Parameters = new Dictionary<string, string>();
            string prefix = CommandPrefix + KeyValueSeparator;
            int index = 0;
            for (int i = 0; i < Content.Length; i++)
            {
                if (Content[i] == WorkUtils.EndOfCommand)
                {
                    index = i;
                    break;
                }
            }
            if (index == 0)
                index = Content.Length - 1;
            string buffer = Encoding.UTF8.GetString(Content, 0, index);
            if (buffer.StartsWith(prefix))
            {
                buffer = buffer.TrimEnd((char)WorkUtils.EndOfCommand);
                index = buffer.IndexOf(KeyValueSeparator, prefix.Length);
                string[] parts = buffer.Split(new char[] { ParameterSeparator }, StringSplitOptions.RemoveEmptyEntries);
                foreach (string part in parts)
                {
                    index = part.IndexOf(KeyValueSeparator);
                    if (index != -1)
                        Parameters.Add(part.Substring(0, index), part.Substring(index + 1));
                }
            }
            return Parameters.Count > 0;
        }

        internal static byte[] BuildCommand(string Command, string Content)
        {
            List<byte> buffer = new List<byte>();
            buffer.AddRange(Encoding.UTF8.GetBytes(CommandPrefix + KeyValueSeparator + Command + ParameterSeparator + Content));
            buffer.Add(WorkUtils.EndOfCommand);
            return buffer.ToArray();
        }
    }

    public enum CompressionMethods
    {
        None = 0,
        Deflate = 1,
        GZip = 2
    }

    public enum HashMethods
    {
        Full = 0,
        Block = 1
    }

    public class WorkSettings
    {
        private CompressionMethods compressionMethod;
        public CompressionMethods CompressionMethod
        {
            get { return compressionMethod; }
            set { compressionMethod = value; }
        }

        private WebProxy proxy = null;
        public WebProxy Proxy
        {
            get { return proxy; }
            set { proxy = value; }
        }

        private bool enableDriveIdentification = false;
        public bool EnableDriveIdentification
        {
            get { return enableDriveIdentification; }
            set { enableDriveIdentification = value; }
        }

        private bool enableNTFS = false;
        public bool EnableNTFS
        {
            get { return enableNTFS; }
            set { enableNTFS = value; }
        }

        private HashMethods hashMethod = HashMethods.Full;
        public HashMethods HashMethod
        {
            get { return hashMethod; }
            set { hashMethod = value; }
        }

        public const int DefaultHashMinSize = 100 * 1024;
        private int hashMinSize = DefaultHashMinSize;
        public int HashMinSize
        {
            get { return hashMinSize; }
            set
            {
                if (value > 0)
                    hashMinSize = value;
            }
        }

        public const int DefaultMaxWorkers = 5;
        private int maxWorkers = DefaultMaxWorkers;
        public int MaxWorkers
        {
            get { return maxWorkers; }
            set
            {
                if (value > 0)
                    maxWorkers = value;
            }
        }

        private SynchronizedDictionary<string, KeyValuePair<string, object>> workProviders = new SynchronizedDictionary<string, KeyValuePair<string, object>>();
        public SynchronizedDictionary<string, KeyValuePair<string, object>> WorkProviders
        {
            get { return workProviders; }
        }

        private Dictionary<string, object> parameters = new Dictionary<string, object>();
        public Dictionary<string, object> Parameters
        {
            get { return parameters; }
            set
            {
                if (value != null)
                    parameters = value;
            }
        }

        private static WorkSettings instance = null;
        public static WorkSettings Instance
        {
            get
            {
                if (instance == null)
                    instance = new WorkSettings();
                return instance;

            }
        }

        public static void Initialize(KeyValuePair<string, string> FileContentIndexer
            , CompressionMethods CompressionMethod
            , WebProxy Proxy
            , bool EnableDriveIdentification
            , bool EnableNTFS
            , int MaxWorkers
            , List<KeyValuePair<string, object>> WorkProviders
            , Dictionary<string, object> Parameters
            , EventHandler<FileProcessEventArgs> OnStorageBeforeFileProcess)
        {
            //I know it's dirty, but...
            LocalFileContentIndexStorage storage = new LocalFileContentIndexStorage(FileContentIndexer);
            LocalFileContentIndexStorage.Instance.BeforeFileProcess += new EventHandler<FileProcessEventArgs>(OnStorageBeforeFileProcess);
            LocalHashStorage.Instance.BeforeFileProcess += new EventHandler<FileProcessEventArgs>(OnStorageBeforeFileProcess);
            WorkSettings.Instance.CompressionMethod = CompressionMethod;
            WorkSettings.Instance.Proxy = Proxy;
            WorkSettings.Instance.EnableDriveIdentification = EnableDriveIdentification;
            WorkSettings.Instance.EnableNTFS = EnableNTFS;
            WorkSettings.instance.Parameters = Parameters;

            WorkSettings.Instance.MaxWorkers = MaxWorkers;
            int workerThreads;
            int completionPortThreads;
            ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads);
            if (workerThreads < MaxWorkers || completionPortThreads < MaxWorkers)
            {
                int max = Math.Max(workerThreads, MaxWorkers);
                max = Math.Max(max, completionPortThreads);
                ThreadPool.SetMinThreads(max, max);
            }

            foreach (KeyValuePair<string, object> item in WorkProviders)
            {
                WorkSettings.Instance.WorkProviders.Add(item.Key, item);
            }
        }
    }

    internal class DynamicInterface
    {
        private static SynchronizedDictionary<string, object> instances = new SynchronizedDictionary<string, object>();

        internal static T GetInterface<T>(string FileName, string TypeName)
        {
            return GetInterface<T>(FileName, TypeName, new object[] { });
        }

        internal static T GetInterface<T>(string FileName, object TypeName, object[] Arguments)
        {
            string typeName = (TypeName is string ? (TypeName as string) : (TypeName as Type).FullName);
            string key = FileName.ToLower() + "|" + typeName.ToLower();
            if (!instances.ContainsKey(key))
            {
                object instance;
                if (TypeName is string)
                {
                    Assembly assembly = Assembly.LoadFrom(FileName);
                    instance = assembly.CreateInstance(TypeName as string, true, BindingFlags.Default, null, Arguments, null, new object[] { });
                }
                else
                    instance = Activator.CreateInstance(TypeName as Type, Arguments);
                instances.Add(key, instance);
            }
            return (T)instances[key];
        }
    }

    public class WorkUtils
    {
        public const string DefaultFileKey = "[Default]";
        public const string ProtocolHTTP = "http";
        public const string ProtocolTCP = "tcp";
        public const string ProtocolFTP = "ftp";
        public const string ProtocolLocal = "local";
        internal static byte EndOfCommand = 0x0;
        internal static byte[] DataEndings = new byte[] { 0x0, 0x1, 0x1, 0x0, 0x1, 0x1, 0x0, 0x2, 0x0 };
        internal static string[] DefaultFileTypes = new string[] { ".txt", ".htm", ".html", ".xml", ".bat", ".lst", ".dhtml", ".js", ".vbs", ".css", ".php", ".cs", ".vb", ".fs", ".java", ".c", ".h", ".cpp", ".asmx", ".ashx", ".aspx", ".xaml", ".dtsx", ".sql" };
        internal const string PathSeparator = "\"";
        public const string ServerAddressSeparator = "/";
        public const string ProtocolSeparator = "://";
        public const string Colon = ":";
        public const string PipeSeparator = "|";
        internal const string LineSeparator = "\r\n";

        internal static void AddVerbose(string Log)
        {
#if(VERBOSE && LOG)
            Add(Log);
#endif
        }

        internal static string ApplicationPath
        {
            get { return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar; }
        }

        internal static void Add(string Log)
        {
            try
            {
                using (StreamWriter writer = File.AppendText(ApplicationPath + "Log" + Process.GetCurrentProcess().Id + ".txt"))
                {
                    writer.WriteLine(DateTime.Now);
                    writer.WriteLine(Log);
                    writer.WriteLine();
                }
            }
            catch (Exception)
            {
                //.....what should we do...
            }
        }

        internal static byte[] CompressBuffer(byte[] Data)
        {
            List<byte> buffer = new List<byte>();
            CompressionMethods compressionMethod = WorkSettings.Instance.CompressionMethod;
            buffer.AddRange(BitConverter.GetBytes((int)compressionMethod));
            buffer.AddRange(BitConverter.GetBytes(Data.Length));
            if (compressionMethod != CompressionMethods.None)
                Compress(compressionMethod, ref Data);
            buffer.AddRange(Data);
            return buffer.ToArray();
        }

        internal static byte[] DecompressBuffer(byte[] Data)
        {
            CompressionMethods compressionMethod = (CompressionMethods)BitConverter.ToInt32(Data, 0);
            int length = BitConverter.ToInt32(Data, 4);
            byte[] buffer = new byte[Data.Length - 8];
            Array.Copy(Data, 8, buffer, 0, buffer.Length);
            if (compressionMethod != CompressionMethods.None)
                Decompress(compressionMethod, ref buffer);
            return buffer;
        }

        internal static byte[] ReadHTTPStreamContent(Stream Source)
        {
            return ReadHTTPStreamContent(Source, true);
        }

        internal static byte[] ReadHTTPStreamContent(Stream Source, bool Decompress)
        {
            byte[] buffer;
            using (MemoryStream writer = new MemoryStream())
            {
                CopyStream(Source, writer);
                buffer = writer.ToArray();
                writer.Close();
            }
            if (Decompress)
                buffer = DecompressBuffer(buffer);
            return buffer;
        }

        internal static string ReadHTTPStreamText(Stream Source)
        {
            return ReadHTTPStreamText(Source, true, Encoding.UTF8);
        }

        internal static string ReadHTTPStreamText(Stream Source, bool Decompress, Encoding Encoding)
        {
            byte[] content = ReadHTTPStreamContent(Source, Decompress);
            return Encoding.GetString(content);
        }

        private const int BufferSize = 1048576;//8192;
        private const int Blocks = 5;

        internal static string HashMD5FileBlocks(string FilePath)
        {
            using (FileStream stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize))
            {
                //5 blocks, each block 10KB
                StringBuilder result = new StringBuilder();
                for (int i = 0; i < Blocks; i++)
                {
                    long segmentSize = stream.Length / Blocks;
                    long pos = i * segmentSize;
                    byte[] buffer = new byte[10 * 1024];
                    stream.Seek(pos, SeekOrigin.Begin);
                    stream.Read(buffer, 0, buffer.Length);
                    using (MemoryStream content = new MemoryStream(buffer))
                    {
                        result.Append(HashMD5Content(content));
                        content.Close();
                    }
                }
                stream.Close();
                return result.ToString();
            }
        }

        internal static string HashMD5File(string FilePath)
        {
            using (FileStream stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize))
            {
                string result = HashMD5Content(stream);
                stream.Close();
                return result;
            }
        }

        public static string HashMD5Content(Stream Content)
        {
            using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
            {
                md5.ComputeHash(Content);
                byte[] hash = md5.Hash;
                return BitConverter.ToString(hash).Replace("-", string.Empty);
            }
        }

        public static string HashMD5Text(string Content)
        {
            using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider())
            {
                md5.ComputeHash(Encoding.UTF8.GetBytes(Content));
                byte[] hash = md5.Hash;
                return BitConverter.ToString(hash).Replace("-", string.Empty);
            }
        }

        internal static bool Decompress(CompressionMethods CompressionMethod, ref byte[] Content)
        {
            try
            {
                if (CompressionMethod != CompressionMethods.None)
                {
                    using (MemoryStream mem = new MemoryStream(Content))
                    {
                        Stream reader = null;
                        switch (CompressionMethod)
                        {
                            case CompressionMethods.Deflate:
                                reader = new DeflateStream(mem, CompressionMode.Decompress, false);
                                break;
                            case CompressionMethods.GZip:
                                reader = new GZipStream(mem, CompressionMode.Decompress, false);
                                break;
                            default:
                                break;
                        }

                        using (MemoryStream writer = new MemoryStream())
                        {
                            CopyStream(reader, writer);
                            writer.Position = 0;
                            Content = new byte[writer.Length];
                            writer.Read(Content, 0, Content.Length);
                            reader.Close();
                            writer.Close();
                        }

                        mem.Close();
                    }
                    return true;
                }
                else
                    return false;
            }
            catch (Exception)
            {
                return false;
            }
        }

        internal static bool Compress(CompressionMethods CompressionMethod, ref byte[] Content)
        {
            try
            {
                if (CompressionMethod != CompressionMethods.None)
                {
                    using (MemoryStream mem = new MemoryStream(Content))
                    {
                        Stream compress = null;
                        using (MemoryStream writer = new MemoryStream())
                        {
                            switch (CompressionMethod)
                            {
                                case CompressionMethods.Deflate:
                                    compress = new DeflateStream(writer, CompressionMode.Compress, true);
                                    break;
                                case CompressionMethods.GZip:
                                    compress = new GZipStream(writer, CompressionMode.Compress, true);
                                    break;
                                default:
                                    break;
                            }
                            compress.Write(Content, 0, Content.Length);
                            compress.Close();
                            Content = new byte[writer.Length];
                            writer.Seek(0, SeekOrigin.Begin);
                            writer.Read(Content, 0, Content.Length);
                            writer.Close();
                        }
                        mem.Close();
                    }
                    return true;
                }
                else
                    return false;
            }
            catch (Exception)
            {
                return false;
            }
        }

        internal static void CopyStream(Stream Source, Stream Destination)
        {
            byte[] buffer = new byte[8 * 1024];
            int n;
            while ((n = Source.Read(buffer, 0, buffer.Length)) > 0)
            {
                Destination.Write(buffer, 0, n);
            }
        }

        internal static string HashMD5FileEx(string FilePath, long FileSize)
        {
            string result;
            LocalHashStorage.Instance.Query(FilePath, FileSize, string.Empty, out result);
            return result;
        }

        internal static bool MatchContains(string Source, string Target)
        {
            if (Target.Contains("*"))
                return Regex.IsMatch(Source, Target);
            else
                return Source.IndexOf(Target, StringComparison.InvariantCultureIgnoreCase) != -1;
        }

        internal static bool MatchFileType(string Source, string[] Targets)
        {
            bool matchFileType = false;
            foreach (string fileType in Targets)
            {
                if (MatchFileType(Source, fileType))
                {
                    matchFileType = true;
                    break;
                }
            }
            return matchFileType;
        }

        internal static bool MatchFileType(string Source, string Target)
        {
            if (Target.Contains("*"))
                return Regex.IsMatch(Source, Target);
            else
                return Source.IndexOf(Target, StringComparison.InvariantCultureIgnoreCase) != -1;
        }

        internal static bool MatchExact(string Source, string Target)
        {
            if (Target.Contains("*"))
                return Regex.IsMatch(Source, Target);
            else
            {
                Source = GetFullPath(Source);
                Target = GetFullPath(Target);
                return Source.ToLower().StartsWith(Target.ToLower());
            }
        }

        internal static BasicFileInfo GetFileInfo(string FileName)
        {
            if (File.Exists(FileName))
            {
                FileInfo info = new FileInfo(FileName);
                return new BasicFileInfo(FileName, info.Length, info.CreationTime, info.LastWriteTime, info.Attributes);
            }
            else
                return new BasicFileInfo(FileName);
        }

        internal static string GetInfoKey(string FileName, SearchTypes SearchType)
        {
            FileInfo info;
            return GetInfoKey(FileName, SearchType, out info);
        }

        internal static string GetInfoKey(string FileName, SearchTypes SearchType, out FileInfo Info)
        {
            Info = new FileInfo(FileName);
            string key = string.Empty;
            if ((SearchType & SearchTypes.Size) == SearchTypes.Size)
                key += WorkUtils.PathSeparator + Info.Length.ToString();
            if ((SearchType & SearchTypes.Name) == SearchTypes.Name)
                key += WorkUtils.PathSeparator + Info.Name.ToLower();
            if ((SearchType & SearchTypes.CreatedDate) == SearchTypes.CreatedDate)
                key += WorkUtils.PathSeparator + Info.CreationTime.ToString();
            if ((SearchType & SearchTypes.ModifiedDate) == SearchTypes.ModifiedDate)
                key += WorkUtils.PathSeparator + Info.LastWriteTime.ToString();
            if ((SearchType & SearchTypes.ModifiedDate) == SearchTypes.Attributes)
                key += WorkUtils.PathSeparator + (int)Info.Attributes;
            if (string.IsNullOrEmpty(key))
                key = DefaultFileKey;
            return key;
        }

        internal static string GetFullPath(string FilePath)
        {
            return !FilePath.EndsWith(Path.DirectorySeparatorChar.ToString()) ? FilePath + Path.DirectorySeparatorChar : FilePath;
        }

        internal static string GetFullURL(string URL)
        {
            return !URL.EndsWith(ServerAddressSeparator) ? URL + ServerAddressSeparator : URL;
        }

        internal static void ParseAddressPort(string URI, out string Address, out int Port)
        {
            int index = URI.IndexOf(ServerAddressSeparator);
            if (index != -1)
            {
                string url = URI.Substring(0, index);
                index = url.IndexOf(Colon);
                Address = url.Substring(0, index);
                Port = Convert.ToInt32(url.Substring(index + 1));
            }
            else
            {
                Address = string.Empty;
                Port = 0;
            }
        }

        public static BaseWorkProvider GetWorkProvider(FileURI FilePath, BaseWork Worker, Dictionary<string, object> Parameters)
        {
            BaseWorkProvider provider = null;
            string protocol;
            string address;
            int port;
            if (ParseProtocol(FilePath.Uri, out protocol, out address, out port))
            {
                switch (protocol)
                {
                    case ProtocolHTTP:
                        provider = new HTTPFileWorkProvider(Worker);
                        break;
                    case ProtocolTCP:
                        provider = new TCPFileWorkProvider(Worker);
                        break;
                    case ProtocolFTP:
                        provider = new FTPFileWorkProvider(Worker, Parameters);
                        break;
                    case ProtocolLocal:
                        provider = new LocalFileWorkProvider(Worker);
                        break;
                    default:
                        KeyValuePair<string, object> found = new KeyValuePair<string, object>();
                        foreach (string item in WorkSettings.Instance.WorkProviders.Keys)
                        {
                            if (item == protocol)
                            {
                                found = WorkSettings.Instance.WorkProviders[item];
                                break;
                            }
                        }
                        if (!string.IsNullOrEmpty(found.Key))
                            provider = DynamicInterface.GetInterface<BaseWorkProvider>(found.Key, found.Value, new object[] { Worker, Parameters });
                        else
                            Worker.OnUknownProtocol(protocol + "(" + FilePath.Uri + ")");
                        break;
                }
            }
            else
                Worker.OnEmptyProtocol();
            return provider;
        }

        public static bool ParseProtocol(string URI, out string Protocol, out string Address, out int Port)
        {
            string relativePath;
            return ParseProtocol(URI, out Protocol, out Address, out Port, out relativePath);
        }

        public static bool ParseProtocol(string URI, out string Protocol, out string Address, out int Port, out string RelativePath)
        {
            int index = URI.IndexOf(ProtocolSeparator);
            if (index != -1)
            {
                Protocol = URI.Substring(0, index);
                string url = URI.Substring(index + ProtocolSeparator.Length);
                index = url.IndexOf(ServerAddressSeparator);
                if (index != -1)
                {
                    int start = url.IndexOf(Colon);
                    if (start != -1 && start < index)
                    {
                        Address = url.Substring(0, start);
                        string buffer = url.Substring(start + Colon.Length, index - start - Colon.Length);
                        if (!int.TryParse(buffer, out Port))
                        {
                            index = -1;
                            Address = string.Empty;
                        }
                    }
                    else
                    {
                        //someting like skydrive/dropbox/AmazonS3/flicker/facebook
                        Address = url.Substring(0, index);
                        Port = 0;
                    }
                    RelativePath = url.Substring(index + ServerAddressSeparator.Length);
                }
                else
                {
                    Address = url;
                    Port = 0;
                    RelativePath = string.Empty;
                }
            }
            else
            {
                Protocol = ProtocolLocal;
                Address = URI;
                Port = 0;
                RelativePath = string.Empty;
            }
            Protocol = Protocol.ToLower();
            return !string.IsNullOrEmpty(Protocol);
        }

        internal static void SendHTTPRequest(string URL, KeyValuePair<FileURI, ManualResetEvent> State, string Data, AsyncCallback CallBack, BaseWork Worker)
        {
            SendHTTPRequest(URL, State, Encoding.UTF8.GetBytes(Data), State.Key.TimeOut, CallBack, Worker);
        }

        internal static void SendHTTPRequest(string URL, KeyValuePair<FileURI, ManualResetEvent> State, byte[] Data, int TimeOut, AsyncCallback CallBack, BaseWork Worker)
        {
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(URL);
            request.Method = "POST";
            if (TimeOut != 0)
            {
                request.ReadWriteTimeout = TimeOut * 1000;
                request.Timeout = TimeOut * 1000;
            }
            if (WorkSettings.Instance.Proxy != null)
                request.Proxy = WorkSettings.Instance.Proxy;
            using (Stream writer = request.GetRequestStream())
            {
                Data = CompressBuffer(Data);
                writer.Write(Data, 0, Data.Length);
                writer.Write(DataEndings, 0, DataEndings.Length);
                writer.Close();
            }
            request.BeginGetResponse(CallBack, new KeyValuePair<HttpWebRequest, KeyValuePair<FileURI, ManualResetEvent>>(request, State));
        }

        public static string NewGuid
        {
            get { return Guid.NewGuid().ToString(); }
        }
    }

    public class MatchFileItem
    {
        private BasicFileInfo originalFile = new BasicFileInfo();
        public BasicFileInfo OriginalFile
        {
            get { return originalFile; }
            set { originalFile = value; }
        }

        private List<BasicFileInfo> matchFiles = new List<BasicFileInfo>();
        public List<BasicFileInfo> MatchFiles
        {
            get { return matchFiles; }
            set { matchFiles = value; }
        }
    }

    [Flags]
    public enum SearchTypes
    {
        None = 0,
        Size = 1,
        Name = 2,
        CreatedDate = 4,
        ModifiedDate = 8,
        Attributes = 16
    }

    public enum MatchTypes
    {
        InfoSame = 1,
        ContentSame = 2,
        ContentContains = 3,
        ContentExtract = 4
    }

    [Serializable]
    public class UserAuth
    {
        private string userName;
        public string UserName
        {
            get { return userName; }
            set { userName = value; }
        }

        private string password;
        public string Password
        {
            get { return password; }
            set { password = value; }
        }

        public UserAuth()
        {
        }

        public UserAuth(string UserName, string Password)
        {
            userName = UserName;
            password = Password;
        }
    }

    [Serializable]
    public class KeyValue
    {
        private string theKey;
        public string Key
        {
            get { return theKey; }
            set { theKey = value; }
        }

        private string theValue;
        public string Value
        {
            get { return theValue; }
            set { theValue = value; }
        }

        public KeyValue()
        {
        }

        public KeyValue(string Key, string Value)
        {
            this.Key = Key;
            this.Value = Value;
        }
    }

    public enum FileUriStatus
    {
        Ready = 0,
        Working = 1,
        Finished = 2,
        Cancelled = 3,
        Failed = 4
    }

    [Serializable]
    public class FileURI : UserAuth
    {
        private const int DefaultTimeOut = 300;

        private string uri;
        public string Uri
        {
            get { return uri; }
            set { uri = value; }
        }

        private FileUriStatus status = FileUriStatus.Ready;
        [XmlIgnore]
        public virtual FileUriStatus Status
        {
            get { return status; }
            set { status = value; }
        }

        private int timeOut = DefaultTimeOut;
        public int TimeOut
        {
            get { return timeOut; }
            set
            {
                timeOut = value;
                if (timeOut <= 0)
                    timeOut = DefaultTimeOut;
            }
        }

#if (LEGACY)
        private ObjectTypes objectType;
        public ObjectTypes ObjectType
        {
            get { return objectType; }
            set { objectType = value; }
        }
#endif

        private List<KeyValue> parameters = new List<KeyValue>();
        public List<KeyValue> Parameters
        {
            get { return parameters; }
            set
            {
                if (value != null)
                    parameters = value;
            }
        }

        internal string GetParameter(string Name)
        {
            string value = string.Empty;
            foreach (KeyValue item in parameters)
            {
                if (item.Key.ToLower() == Name.ToLower())
                {
                    value = item.Value;
                    break;
                }
            }
            return value;
        }

        public FileURI()
        {
        }

        public FileURI(string FilePath)
            : this(FilePath, string.Empty, string.Empty, 0, null)
        {
        }

        public FileURI(string FilePath, string UserName, string Password, int TimeOut, List<KeyValue> Parameters)
        {
            uri = FilePath;
            this.UserName = UserName;
            this.Password = Password;
            timeOut = TimeOut;
            if (timeOut <= 0)
                timeOut = DefaultTimeOut;
            this.Parameters = Parameters;
        }
    }

    public enum WorkModes
    {
        Client = 0,
        Server = 1
    }

    public enum WorkProgressTypes
    {
        FileFound = 0,
        FileAccess = 1,
        Network = 2,
        Detail = 4,
        Error = 8
    }

    [Flags]
    public enum OverwriteModes
    {
        None = 0,
        Larger = 1,
        Smaller = 2,
        Newer = 4,
        Older = 8
    }

    public class BasicFileInfo
    {
        private string fileName;
        private long fileSize;
        private DateTime createdDate;
        private DateTime lastModifiedDate;
        private FileAttributes attributes;

        public string FileName
        {
            get { return fileName; }
            set { fileName = value; }
        }

        public FileAttributes Attributes
        {
            get { return attributes; }
            set { attributes = value; }
        }

        public long FileSize
        {
            get { return fileSize; }
            set { fileSize = value; }
        }

        public DateTime CreatedDate
        {
            get { return createdDate; }
            set { createdDate = value; }
        }

        public DateTime LastModifiedDate
        {
            get { return lastModifiedDate; }
            set { lastModifiedDate = value; }
        }

        public BasicFileInfo()
        {
        }

        public BasicFileInfo(string FileName)
        {
            fileName = FileName;
        }

        public BasicFileInfo(string FileName, long FileSize, DateTime CreatedDate, DateTime LastModifiedDate, FileAttributes Attributes)
        {
            fileName = FileName;
            fileSize = FileSize;
            createdDate = CreatedDate;
            lastModifiedDate = LastModifiedDate;
            attributes = Attributes;
        }
    }

    public abstract class BaseWork
    {
        private WorkModes workMode = WorkModes.Client;
        public WorkModes WorkMode
        {
            get { return workMode; }
            set { workMode = value; }
        }

        public event EventHandler<ProgressEventArgs> Progress;

        protected internal void OnProgress(string Key, List<BasicFileInfo> MatchFiles)
        {
            if (Progress != null)
                Progress(this, new ProgressEventArgs(new KeyValuePair<string, List<BasicFileInfo>>(Key, MatchFiles)));
        }

        protected internal void OnProgress(WorkProgressTypes ProgressType, string Status)
        {
            if (Progress != null)
                Progress(this, new ProgressEventArgs(ProgressType, Status));
        }

        protected internal void OnEmptyProtocol()
        {
            OnProgress(WorkProgressTypes.FileAccess | WorkProgressTypes.Error, "Empty protocol");
        }

        protected internal void OnUknownProtocol(string FilePath)
        {
            OnProgress(WorkProgressTypes.FileAccess | WorkProgressTypes.Error, "Unknown protocol:" + FilePath);
        }

        public virtual List<BasicFileInfo> FindAll(MatchTypes MatchType, Dictionary<string, MatchFileItem> Result, string MatchFolder)
        {
            List<BasicFileInfo> files = new List<BasicFileInfo>();
            foreach (KeyValuePair<string, MatchFileItem> item in Result)
            {
                if (item.Value.MatchFiles.Count > 1 || MatchType == MatchTypes.InfoSame)
                {
                    for (int i = 0; i < item.Value.MatchFiles.Count; i++)
                    {
                        if (string.IsNullOrEmpty(item.Value.OriginalFile.FileName))
                        {
                            if (!WorkUtils.MatchContains(item.Value.MatchFiles[i].FileName, MatchFolder))
                                item.Value.OriginalFile = item.Value.MatchFiles[i];
                        }
                        if ((!string.IsNullOrEmpty(item.Value.OriginalFile.FileName) && item.Value.MatchFiles[i] != item.Value.OriginalFile)
                            || (string.IsNullOrEmpty(MatchFolder) && i > 0)
                            || (WorkUtils.MatchContains(item.Value.MatchFiles[i].FileName, MatchFolder)))
                            files.Add(item.Value.MatchFiles[i]);
                    }
                    //make sure the original is there
                    if (string.IsNullOrEmpty(item.Value.OriginalFile.FileName))
                    {
                        item.Value.OriginalFile = item.Value.MatchFiles[0];
                        item.Value.MatchFiles.RemoveAt(0);
                        files.Remove(item.Value.OriginalFile);
                    }
                }
            }
            return files;
        }

        /// <summary>
        /// be carefull!!!
        /// </summary>
        /// <param name="Files"></param>
        public virtual List<BasicFileInfo> DeleteAll(List<BasicFileInfo> Files, FileURI[] FilePaths)
        {
            List<BasicFileInfo> result = new List<BasicFileInfo>();
#if(!DEBUG)
            foreach (BasicFileInfo item in Files)
            {
                if (File.Exists(item.FileName))
                {
                    try
                    {
                        File.Delete(item.FileName);
                        result.Add(item);
                    }
                    catch (Exception ex)
                    {
                        OnProgress(WorkProgressTypes.FileAccess, ex.ToString());
                    }
                }
            }
#endif
            return result;
        }

        public virtual Dictionary<string, MatchFileItem> Find(string[] FilePaths, string[] IncludesFileTypes, string Keyword, SearchTypes SearchType, MatchTypes MatchType, HashMethods HashMethod)
        {
            if (FilePaths != null && FilePaths.Length > 0)
            {
                FileURI[] uris = new FileURI[FilePaths.Length];
                for (int i = 0; i < FilePaths.Length; i++)
                {
                    FileURI uri = new FileURI(FilePaths[i]);
                    uris[i] = uri;
                }
                return Find(uris, IncludesFileTypes, Keyword, SearchType, MatchType, HashMethod);
            }
            else
                return new Dictionary<string, MatchFileItem>();
        }

        public abstract Dictionary<string, MatchFileItem> Find(FileURI[] FilePaths, string[] IncludesFileTypes, string Keyword, SearchTypes SearchType, MatchTypes MatchType, HashMethods HashMethod);

    }

    public class ProgressEventArgs : EventArgs
    {
        private KeyValuePair<string, List<BasicFileInfo>> status;
        public KeyValuePair<string, List<BasicFileInfo>> Status
        {
            get { return status; }
            set { status = value; }
        }

        private WorkProgressTypes progressType;
        public WorkProgressTypes ProgressType
        {
            get { return progressType; }
            set { progressType = value; }
        }

        private string log;
        public string Log
        {
            get { return log; }
            set { log = value; }
        }

        public ProgressEventArgs(KeyValuePair<string, List<BasicFileInfo>> Status)
        {
            status = Status;
            progressType = WorkProgressTypes.FileFound;
        }

        public ProgressEventArgs(WorkProgressTypes ProgressType, string Log)
        {
            progressType = ProgressType;
            log = Log;
        }
    }

    public class DataEventArgs : EventArgs
    {
        private long length;
        public long Length
        {
            get { return length; }
            set { length = value; }
        }

        private string content;
        public string Content
        {
            get { return content; }
            set { content = value; }
        }

        private string response;
        public string Response
        {
            get { return response; }
            set { response = value; }
        }

        private byte[] buffer;
        public byte[] Buffer
        {
            get { return buffer; }
            set { buffer = value; }
        }
    }

    public class StateDataEventArgs : DataEventArgs
    {

        private bool result = false;
        public bool Result
        {
            get { return result; }
            set { result = value; }
        }

        private string state;
        public string State
        {
            get { return state; }
            set { state = value; }
        }
    }

    public enum TransferMethods
    {
        Download = 0,
        Upload = 1,
        Synchronize = 2
    }

    #endregion


    #region WorkV7
    public abstract class BaseWorkProvider : ICancellable
    {
        public const string ParameterEncoding = "Encoding";
        public const string ParameterPort = "Port";
        public const string ParameterSSL = "SSL";
        public const string ParameterSize = "Size";
        public const string ParameterFilePath = "Path";
        protected BaseWork worker;
        protected List<BasicFileInfo> files;
        protected SynchronizedDictionary<string, BasicFileInfo> deleteResult;
        protected FileURI fileAccess;
        protected FileURI filePath;
        protected string[] includesFileTypes;
        protected SearchTypes searchType;
        protected string keyword;
        protected string groupKey = string.Empty;
        protected MatchTypes matchType;
        protected HashMethods hashMethod;
        protected SynchronizedDictionary<string, MatchFileItem> matchResult;
        protected SynchronizedDictionary<string, List<BasicFileInfo>> findResult;
        protected Dictionary<string, object> parameters;
        protected SynchronizedDictionary<string, KeyValuePair<BasicFileInfo, BasicFileInfo>> transferedResult;
        protected TransferMethods transferMethod;
        protected OverwriteModes overwriteMode;
        protected FileURI source;
        protected FileURI target;

        public BaseWorkProvider()
        {
        }

        public BaseWorkProvider(BaseWork Worker, Dictionary<string, object> Parameters)
        {
            worker = Worker;
            parameters = Parameters;
            if (parameters == null)
                parameters = new Dictionary<string, object>();
        }

        public FileURI FilePath
        {
            get { return filePath; }
        }

        private int matchCount = 0;
        public int MatchCount
        {
            get { return matchCount; }
            set { matchCount = value; }
        }

        private bool isCancelled = false;
        public virtual bool IsCancelled
        {
            get { return isCancelled; }
            protected set { isCancelled = value; }
        }

        protected Encoding GetEncoding(Encoding Default)
        {
            string parameter = filePath.GetParameter(ParameterEncoding);
            Encoding encoding;
            if (!string.IsNullOrEmpty(parameter))
                encoding = Encoding.GetEncoding(parameter);
            else
                encoding = Default;
            return encoding;
        }

        protected bool GetSSL(bool Default)
        {
            string parameter = filePath.GetParameter(ParameterSSL);
            bool use;
            if (!string.IsNullOrEmpty(parameter))
                use = Convert.ToBoolean(parameter);
            else
                use = Default;
            return use;
        }

        protected int GetSize(int Default)
        {
            string parameter = filePath.GetParameter(ParameterSize);
            int use;
            if (!string.IsNullOrEmpty(parameter))
                use = Convert.ToInt32(parameter);
            else
                use = Default;
            return use;
        }
        public abstract void Cancel(FileURI[] FilePaths);

        public abstract string Name { get; }
        public abstract string ImageFile { get; }
        public abstract string Description { get; }
        public abstract List<string> SupportedProtocols { get; }
        public virtual void Find(FileURI FilePath, string[] IncludesFileTypes, string Keyword, SearchTypes SearchType, MatchTypes MatchType, HashMethods HashMethod, SynchronizedDictionary<string, List<BasicFileInfo>> SameFiles)
        {
            filePath = FilePath;
            includesFileTypes = IncludesFileTypes;
            searchType = SearchType;
            keyword = Keyword;
            matchType = MatchType;
            hashMethod = HashMethod;
            findResult = SameFiles;
            string protocol;
            string address;
            int port;
            string relativePath;
            if (WorkUtils.ParseProtocol(FilePath.Uri, out protocol, out address, out port, out relativePath))
                groupKey = protocol;
        }
        public abstract void Match(List<BasicFileInfo> Files, FileURI FileAccess, string Keyword, SearchTypes SearchType, MatchTypes MatchType, SynchronizedDictionary<string, MatchFileItem> Result);
        public abstract void Delete(List<BasicFileInfo> Files, FileURI FileAccess, SynchronizedDictionary<string, BasicFileInfo> Result);
        public abstract void Transfer(FileURI Source, FileURI Target, string[] IncludesFileTypes, TransferMethods TransferMethod, SearchTypes SearchType, OverwriteModes OverwriteMode, SynchronizedDictionary<string, KeyValuePair<BasicFileInfo, BasicFileInfo>> TransferedFiles);

        protected void MatchItem(string Item, string Name, DateTime Date)
        {
            bool matchFileType;
            bool hasFileType;
            bool found = MatchFile(Item, out matchFileType, out hasFileType);
            switch (matchType)
            {
                case MatchTypes.ContentSame:
                    if (found)
                    {
                        string key = string.Empty;
                        if ((searchType & SearchTypes.Size) == SearchTypes.Size)
                            key += WorkUtils.PathSeparator + Item.Length.ToString();
                        if ((searchType & SearchTypes.Name) == SearchTypes.Name)
                            key += WorkUtils.PathSeparator + Name.ToLower();
                        if ((searchType & SearchTypes.ModifiedDate) == SearchTypes.ModifiedDate)
                            key += WorkUtils.PathSeparator + Date.ToString();
                        if (!findResult.ContainsKey(key))
                            findResult.Add(key, new List<BasicFileInfo>());
                        findResult[key].Add(new BasicFileInfo(Item, Item.Length, Date, Date, FileAttributes.Normal));
                    }
                    break;
                case MatchTypes.ContentContains:
                case MatchTypes.ContentExtract:
                    if (matchFileType && !string.IsNullOrEmpty(keyword))
                    {
                        if (!hasFileType)
                            matchFileType = WorkUtils.MatchFileType(Item, WorkUtils.DefaultFileTypes);
                        if (matchFileType)
                        {
                            string key = keyword;
                            if (!findResult.ContainsKey(key))
                                findResult.Add(key, new List<BasicFileInfo>());
                            findResult[key].Add(new BasicFileInfo(Item, Item.Length, Date, Date, FileAttributes.Normal));
                        }
                    }
                    break;
                default:
                    if (found)
                    {
                        if (!hasFileType && !string.IsNullOrEmpty(keyword))
                            found = WorkUtils.MatchContains(Item, keyword);
                        if (found)
                        {
                            string key = keyword;
                            if (string.IsNullOrEmpty(key))
                                key = groupKey;
                            if (!findResult.ContainsKey(key))
                                findResult.Add(key, new List<BasicFileInfo>());
                            findResult[key].Add(new BasicFileInfo(Item, Item.Length, Date, Date, FileAttributes.Normal));
                        }
                    }
                    break;
            }
        }

        protected bool MatchFile(string FilePath, out bool MatchFileType, out bool HasFileType)
        {
            bool found;
            HasFileType = includesFileTypes != null && includesFileTypes.Length > 0;
            if (HasFileType)
            {
                MatchFileType = WorkUtils.MatchFileType(FilePath, includesFileTypes);
                if (MatchFileType)
                {
                    if (!string.IsNullOrEmpty(keyword))
                        found = WorkUtils.MatchContains(FilePath, keyword);
                    else
                        found = true;
                }
                else
                    found = false;
            }
            else
            {
                MatchFileType = true;
                if (!string.IsNullOrEmpty(keyword))
                    found = WorkUtils.MatchContains(FilePath, keyword);
                else
                    found = true;
            }
            return found;
        }

        protected void MatchFiles(string[] FilePaths)
        {
            foreach (string item in FilePaths)
            {
                bool matchFileType;
                bool hasFileType;
                bool found = MatchFile(item, out matchFileType, out hasFileType);
                switch (matchType)
                {
                    case MatchTypes.ContentSame:
                        if (found)
                        {
                            FileInfo info;
                            string key = WorkUtils.GetInfoKey(item, searchType, out info);
                            if (!findResult.ContainsKey(key))
                                findResult.Add(key, new List<BasicFileInfo>());
                            findResult[key].Add(new BasicFileInfo(item, info.Length, info.CreationTime, info.LastWriteTime, info.Attributes));
                        }
                        break;
                    case MatchTypes.ContentContains:
                    case MatchTypes.ContentExtract:
                        if (matchFileType && !string.IsNullOrEmpty(keyword))
                        {
                            if (!hasFileType)
                                matchFileType = WorkUtils.MatchFileType(item, WorkUtils.DefaultFileTypes);
                            if (matchFileType)
                            {
                                string key = keyword;
                                if (!findResult.ContainsKey(key))
                                    findResult.Add(key, new List<BasicFileInfo>());
                                findResult[key].Add(WorkUtils.GetFileInfo(item));
                            }
                        }
                        break;
                    default:
                        if (found)
                        {
                            if (!hasFileType && !string.IsNullOrEmpty(keyword))
                                found = WorkUtils.MatchContains(item, keyword);
                            if (found)
                            {
                                string key = keyword;
                                if (string.IsNullOrEmpty(key))
                                    key = groupKey;
                                if (!findResult.ContainsKey(key))
                                    findResult.Add(key, new List<BasicFileInfo>());
                                findResult[key].Add(WorkUtils.GetFileInfo(item));
                            }
                        }
                        break;
                }
            }
        }

    }

    internal abstract class RemoteFileWorkProvider : BaseWorkProvider
    {
        internal RemoteFileWorkProvider(BaseWork Worker)
            : base(Worker, null)
        {
        }

        protected byte[] BuildDiscover(FileURI FilePath)
        {
            StringBuilder command = new StringBuilder();
            command.Append(RemoteCommander.ParameterUserName + RemoteCommander.KeyValueSeparator + FilePath.UserName + RemoteCommander.ParameterSeparator);
            command.Append(RemoteCommander.ParameterPassword + RemoteCommander.KeyValueSeparator + FilePath.Password + RemoteCommander.ParameterSeparator);
            return RemoteCommander.BuildCommand(RemoteCommander.CommandDiscover, command.ToString());
        }

        protected byte[] BuildMatchCommand()
        {
            StringBuilder command = new StringBuilder();
            command.Append(RemoteCommander.ParameterUserName + RemoteCommander.KeyValueSeparator + fileAccess.UserName + RemoteCommander.ParameterSeparator);
            command.Append(RemoteCommander.ParameterPassword + RemoteCommander.KeyValueSeparator + fileAccess.Password + RemoteCommander.ParameterSeparator);
            command.Append(RemoteCommander.ParameterFilePaths + RemoteCommander.KeyValueSeparator);
            foreach (BasicFileInfo item in files)
            {
                command.Append(item.FileName + WorkUtils.PathSeparator + item.FileSize + WorkUtils.PathSeparator + item.CreatedDate + WorkUtils.PathSeparator + item.LastModifiedDate + WorkUtils.PathSeparator + item.Attributes + RemoteCommander.FileItemSeparator);
            }
            command.Append(RemoteCommander.ParameterSeparator);
            command.Append(RemoteCommander.ParameterSearchType + RemoteCommander.KeyValueSeparator + (int)searchType + RemoteCommander.ParameterSeparator);
            command.Append(RemoteCommander.ParameterKeyword + RemoteCommander.KeyValueSeparator + keyword + RemoteCommander.ParameterSeparator);
            command.Append(RemoteCommander.ParameterMatchType + RemoteCommander.KeyValueSeparator + (int)matchType + RemoteCommander.ParameterSeparator);
            command.Append(RemoteCommander.ParameterHashMethod + RemoteCommander.KeyValueSeparator + (int)hashMethod + RemoteCommander.ParameterSeparator);
            return RemoteCommander.BuildCommand(RemoteCommander.CommandMatch, command.ToString());
        }

        protected byte[] BuildDeleteCommand()
        {
            StringBuilder command = new StringBuilder();
            command.Append(RemoteCommander.ParameterUserName + RemoteCommander.KeyValueSeparator + fileAccess.UserName + RemoteCommander.ParameterSeparator);
            command.Append(RemoteCommander.ParameterPassword + RemoteCommander.KeyValueSeparator + fileAccess.Password + RemoteCommander.ParameterSeparator);
            command.Append(RemoteCommander.ParameterFilePaths + RemoteCommander.KeyValueSeparator);
            foreach (BasicFileInfo item in files)
            {
                command.Append(item.FileName + WorkUtils.PathSeparator + item.FileSize + WorkUtils.PathSeparator + item.CreatedDate + WorkUtils.PathSeparator + item.LastModifiedDate + WorkUtils.PathSeparator + item.Attributes + RemoteCommander.FileItemSeparator);
            }
            command.Append(RemoteCommander.ParameterSeparator);
            return RemoteCommander.BuildCommand(RemoteCommander.CommandDelete, command.ToString());
        }

        protected byte[] ProcessRequest(byte[] Data, KeyValuePair<FileURI, ManualResetEvent> State)
        {
            byte[] result = null;
            Dictionary<string, string> parameters;
            string protocol;
            string address;
            int port;
            string relativePath;
            if (RemoteCommander.ParseCommand(Data, out parameters))
            {
                switch (parameters[RemoteCommander.CommandPrefix])
                {
                    case RemoteCommander.CommandDelete:
                        if (parameters.ContainsKey(RemoteCommander.ParameterFilePaths))
                        {
                            if (WorkUtils.ParseProtocol(fileAccess.Uri, out protocol, out address, out port))
                            {
                                string filePaths = parameters[RemoteCommander.ParameterFilePaths];
                                string[] deletedFiles = filePaths.Split(new string[] { RemoteCommander.FileItemSeparator }, StringSplitOptions.RemoveEmptyEntries);
                                foreach (string file in deletedFiles)
                                {
                                    string[] parts = file.Split(new string[] { WorkUtils.PathSeparator }, StringSplitOptions.None);
                                    deleteResult.Add(parts[0], new BasicFileInfo(parts[0], Convert.ToInt64(parts[1]), Convert.ToDateTime(parts[2]), Convert.ToDateTime(parts[3]), (FileAttributes)Convert.ToInt32(parts[4])));
                                }
                            }
                        }
                        State.Value.Set();
                        break;
                    case RemoteCommander.CommandQuery:
                        if (parameters.ContainsKey(RemoteCommander.ParameterFilePaths))
                        {
                            if (WorkUtils.ParseProtocol(State.Key.Uri, out protocol, out address, out port))
                            {
                                string filePaths = parameters[RemoteCommander.ParameterFilePaths];
                                string[] groups = filePaths.Split(new string[] { RemoteCommander.FileGroupSeparator }, StringSplitOptions.RemoveEmptyEntries);
                                foreach (string group in groups)
                                {
                                    string[] files = group.Split(new string[] { RemoteCommander.FileItemSeparator }, StringSplitOptions.RemoveEmptyEntries);
                                    string key = files[0];
                                    if (!findResult.ContainsKey(key))
                                        findResult.Add(key, new List<BasicFileInfo>());
                                    for (int i = 1; i < files.Length; i++)
                                    {
                                        string[] parts = files[i].Split(new string[] { WorkUtils.PathSeparator }, StringSplitOptions.None);
                                        findResult[key].Add(new BasicFileInfo(protocol + WorkUtils.ProtocolSeparator + address + WorkUtils.Colon + port + WorkUtils.ServerAddressSeparator + parts[0], Convert.ToInt64(parts[1]), Convert.ToDateTime(parts[2]), Convert.ToDateTime(parts[3]), (FileAttributes)Convert.ToInt32(parts[4])));
                                    }
                                }
                            }
                        }
                        State.Value.Set();
                        break;
                    case RemoteCommander.CommandDiscover:
                        FileURI filePath = State.Key;
                        if (WorkUtils.ParseProtocol(filePath.Uri, out protocol, out address, out port, out relativePath))
                        {
                            string allowedFilePaths;
                            if (parameters.ContainsKey(RemoteCommander.ParameterFilePaths))
                                allowedFilePaths = parameters[RemoteCommander.ParameterFilePaths];
                            else
                                allowedFilePaths = string.Empty;
                            string[] allowedFilePathSegments = allowedFilePaths.Split(new string[] { RemoteCommander.FileItemSeparator }, StringSplitOptions.RemoveEmptyEntries);
                            string[] allPathSements = relativePath.Split(new string[] { WorkUtils.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
                            List<string> filteredFilePaths = new List<string>();
                            foreach (string file in allPathSements)
                            {
                                foreach (string item in allowedFilePathSegments)
                                {
                                    if (WorkUtils.MatchExact(file, item))
                                    {
                                        filteredFilePaths.Add(file);
                                        break;
                                    }
                                }
                            }
                            if (filteredFilePaths.Count > 0)
                            {
                                StringBuilder command = new StringBuilder();
                                command.Append(RemoteCommander.ParameterUserName + RemoteCommander.KeyValueSeparator + filePath.UserName + RemoteCommander.ParameterSeparator);
                                command.Append(RemoteCommander.ParameterPassword + RemoteCommander.KeyValueSeparator + filePath.Password + RemoteCommander.ParameterSeparator);
                                relativePath = string.Join(WorkUtils.PathSeparator, filteredFilePaths.ToArray());
                                command.Append(RemoteCommander.ParameterFilePaths + RemoteCommander.KeyValueSeparator + relativePath + RemoteCommander.ParameterSeparator);
                                command.Append(RemoteCommander.ParameterIncludeFileTypes + RemoteCommander.KeyValueSeparator + string.Join(WorkUtils.PathSeparator, includesFileTypes) + RemoteCommander.ParameterSeparator);
                                command.Append(RemoteCommander.ParameterSearchType + RemoteCommander.KeyValueSeparator + (int)searchType + RemoteCommander.ParameterSeparator);
                                command.Append(RemoteCommander.ParameterKeyword + RemoteCommander.KeyValueSeparator + keyword + RemoteCommander.ParameterSeparator);
                                command.Append(RemoteCommander.ParameterMatchType + RemoteCommander.KeyValueSeparator + (int)matchType + RemoteCommander.ParameterSeparator);
                                command.Append(RemoteCommander.ParameterHashMethod + RemoteCommander.KeyValueSeparator + (int)hashMethod + RemoteCommander.ParameterSeparator);
                                result = RemoteCommander.BuildCommand(RemoteCommander.CommandQuery, command.ToString());
                            }
                            else
                                worker.OnProgress(WorkProgressTypes.FileAccess | WorkProgressTypes.Error, "no remote access allowed files to search");
                        }
                        break;
                    case RemoteCommander.CommandMatch:
                        if (parameters.ContainsKey(RemoteCommander.ParameterFilePaths))
                        {
                            if (WorkUtils.ParseProtocol(fileAccess.Uri, out protocol, out address, out port))
                            {
                                string filePaths = parameters[RemoteCommander.ParameterFilePaths];
                                string[] groups = filePaths.Split(new string[] { RemoteCommander.FileGroupSeparator }, StringSplitOptions.RemoveEmptyEntries);
                                if (!string.IsNullOrEmpty(address))
                                {
                                    foreach (string group in groups)
                                    {
                                        string[] files = group.Split(new string[] { RemoteCommander.FileItemSeparator }, StringSplitOptions.RemoveEmptyEntries);
                                        string key = files[0];
                                        if (!matchResult.ContainsKey(key))
                                            matchResult.Add(key, new MatchFileItem());
                                        for (int i = 1; i < files.Length; i++)
                                        {
                                            MatchCount++;
                                            string[] parts = files[i].Split(new string[] { WorkUtils.PathSeparator }, StringSplitOptions.None);
                                            matchResult[key].MatchFiles.Add(new BasicFileInfo(protocol + WorkUtils.ProtocolSeparator + address + WorkUtils.Colon + port + WorkUtils.ServerAddressSeparator + parts[0], Convert.ToInt64(parts[1]), Convert.ToDateTime(parts[2]), Convert.ToDateTime(parts[3]), (FileAttributes)Convert.ToInt32(parts[4])));
                                        }
                                    }
                                }
                            }
                        }
                        State.Value.Set();
                        break;
                    case RemoteCommander.CommandAccessDenied:
                        worker.OnProgress(WorkProgressTypes.FileAccess | WorkProgressTypes.Error, "remote access denied");
                        State.Value.Set();
                        break;
                    case RemoteCommander.CommandNoCommand:
                        worker.OnProgress(WorkProgressTypes.FileAccess | WorkProgressTypes.Error, "remote command empty");
                        State.Value.Set();
                        break;
                    case RemoteCommander.CommandNoData:
                        worker.OnProgress(WorkProgressTypes.FileAccess | WorkProgressTypes.Error, "remote access no data");
                        State.Value.Set();
                        break;
                    default:
                        State.Value.Set();
                        break;
                }
            }
            return result;
        }
    }

    internal class TCPFileWorkProvider : RemoteFileWorkProvider
    {
        private ManualResetEvent remoteDone = null;

        internal TCPFileWorkProvider(BaseWork Worker)
            : base(Worker)
        {
        }

        public override string Name
        {
            get { return "TCP"; }
        }

        public override string Description
        {
            get { return "TCP client"; }
        }

        public override string ImageFile
        {
            get { return "/Images/ServerWeb2.png"; }
        }

        public override List<string> SupportedProtocols
        {
            get { return new List<string>(new string[] { "tcp" }); }
        }

        public override void Cancel(FileURI[] FilePaths)
        {
            if (remoteDone != null)
                remoteDone.Set();
            IsCancelled = true;
        }

        public override void Find(FileURI FilePath, string[] IncludesFileTypes, string Keyword, SearchTypes SearchType, MatchTypes MatchType, HashMethods HashMethod, SynchronizedDictionary<string, List<BasicFileInfo>> SameFiles)
        {
            base.Find(FilePath, IncludesFileTypes, Keyword, SearchType, MatchType, HashMethod, SameFiles);
            Connect(filePath, OnFindClientConnected);
        }

        private void Connect(FileURI FilePath, EventHandler<DataTransferEventArgs> OnClientConnected)
        {
            string address;
            int port;
            string protocol;
            if (WorkUtils.ParseProtocol(FilePath.Uri, out protocol, out address, out port))
            {
                remoteDone = new ManualResetEvent(false);
                KeyValuePair<FileURI, ManualResetEvent> state = new KeyValuePair<FileURI, ManualResetEvent>(FilePath, remoteDone);
                AsynchronousClient client = new AsynchronousClient();
                client.Connected += new EventHandler<DataTransferEventArgs>(OnClientConnected);
                client.Received += new EventHandler<DataTransferEventArgs>(OnClientReceived);
                client.Sent += new EventHandler<DataTransferEventArgs>(OnClientSent);
                client.Failed += new EventHandler<DataTransferEventArgs>(OnClientFailed);
                client.State = state;
                client.Connect(address, port);
                remoteDone.WaitOne(FilePath.TimeOut * 1000);
            }
        }

        private void OnClientFailed(object sender, DataTransferEventArgs e)
        {
            worker.OnProgress(WorkProgressTypes.Network | WorkProgressTypes.Error, "Remote connection failed:" + e.Content);
            KeyValuePair<FileURI, ManualResetEvent> state = (KeyValuePair<FileURI, ManualResetEvent>)((AsynchronousClient)sender).State;
            state.Value.Set();
        }

        private void OnClientSent(object sender, DataTransferEventArgs e)
        {
            worker.OnProgress(WorkProgressTypes.Network, "remote command sent");
            e.Worker.Receive();
        }

        private void OnClientReceived(object sender, DataTransferEventArgs e)
        {
            worker.OnProgress(WorkProgressTypes.Network, "remote command received:" + e.Content);
            KeyValuePair<FileURI, ManualResetEvent> state = (KeyValuePair<FileURI, ManualResetEvent>)((AsynchronousClient)sender).State;
            byte[] result = ProcessRequest(e.Buffer, state);
            e.Buffer = null;
            if (result != null)
                ((AsynchronousClient)sender).Send(WorkSettings.Instance.CompressionMethod, result);
        }

        private void OnFindClientConnected(object sender, DataTransferEventArgs e)
        {
            SendRequest((AsynchronousClient)sender, e, BuildDiscover(filePath));
        }

        private void SendRequest(AsynchronousClient sender, DataTransferEventArgs e, byte[] Command)
        {
            worker.OnProgress(WorkProgressTypes.Network, "connected to remote server:" + e.Content);
            KeyValuePair<FileURI, ManualResetEvent> state = (KeyValuePair<FileURI, ManualResetEvent>)((AsynchronousClient)sender).State;
            FileURI filePath = state.Key;
            sender.Send(WorkSettings.Instance.CompressionMethod, Command);
        }

        public override void Match(List<BasicFileInfo> Files, FileURI FileAccess, string Keyword, SearchTypes SearchType, MatchTypes MatchType, SynchronizedDictionary<string, MatchFileItem> Result)
        {
            files = Files;
            fileAccess = FileAccess;
            keyword = Keyword;
            searchType = SearchType;
            matchType = MatchType;
            matchResult = Result;
            Connect(fileAccess, OnMatchClientConnected);
        }

        private void OnMatchClientConnected(object sender, DataTransferEventArgs e)
        {
            SendRequest((AsynchronousClient)sender, e, BuildMatchCommand());
        }

        public override void Delete(List<BasicFileInfo> Files, FileURI FileAccess, SynchronizedDictionary<string, BasicFileInfo> Result)
        {
            files = Files;
            fileAccess = FileAccess;
            deleteResult = Result;
            Connect(fileAccess, OnDeleteClientConnected);
        }

        private void OnDeleteClientConnected(object sender, DataTransferEventArgs e)
        {
            SendRequest((AsynchronousClient)sender, e, BuildDeleteCommand());
        }

        public override void Transfer(FileURI Source, FileURI Target, string[] IncludesFileTypes, TransferMethods TransferMethod, SearchTypes SearchType, OverwriteModes OverwriteMode, SynchronizedDictionary<string, KeyValuePair<BasicFileInfo, BasicFileInfo>> TransferedFiles)
        {
            throw new NotImplementedException();
        }
    }

    internal class HTTPFileWorkProvider : RemoteFileWorkProvider
    {
        private ManualResetEvent remoteDone = null;

        internal HTTPFileWorkProvider(BaseWork Worker)
            : base(Worker)
        {
        }

        public override string Name
        {
            get { return "HTTP"; }
        }

        public override string Description
        {
            get { return "HTTP client"; }
        }

        public override string ImageFile
        {
            get { return "/Images/Web2.png"; }
        }

        public override List<string> SupportedProtocols
        {
            get { return new List<string>(new string[] { "http" }); }
        }

        public override void Cancel(FileURI[] FilePaths)
        {
            if (remoteDone != null)
                remoteDone.Set();
            IsCancelled = true;
        }

        public override void Find(FileURI FilePath, string[] IncludesFileTypes, string Keyword, SearchTypes SearchType, MatchTypes MatchType, HashMethods HashMethod, SynchronizedDictionary<string, List<BasicFileInfo>> SameFiles)
        {
            base.Find(FilePath, IncludesFileTypes, Keyword, SearchType, MatchType, HashMethod, SameFiles);
            SendRequest(filePath, BuildDiscover(filePath), OnHTTPGetDiscoverResult);
        }

        private void SendRequest(FileURI FilePath, byte[] Command, AsyncCallback OnRequestResult)
        {
            string protocol;
            string address;
            int port;
            if (WorkUtils.ParseProtocol(FilePath.Uri, out protocol, out address, out port))
            {
                if (port == 0)
                    port = 80;
                if (!string.IsNullOrEmpty(address) && port > 0)
                {
                    remoteDone = new ManualResetEvent(false);
                    KeyValuePair<FileURI, ManualResetEvent> state = new KeyValuePair<FileURI, ManualResetEvent>(FilePath, remoteDone);
                    WorkUtils.SendHTTPRequest(string.Format("http://{0}:{1}", address, port), state, Command, FilePath.TimeOut * 1000, OnRequestResult, worker);
                    remoteDone.WaitOne(FilePath.TimeOut * 1000);
                }
            }
        }

        private void OnHTTPGetDiscoverResult(IAsyncResult ar)
        {
            KeyValuePair<HttpWebRequest, KeyValuePair<FileURI, ManualResetEvent>> state = (KeyValuePair<HttpWebRequest, KeyValuePair<FileURI, ManualResetEvent>>)ar.AsyncState;
            HttpWebResponse response = (HttpWebResponse)state.Key.EndGetResponse(ar);
            byte[] result = ProcessRequest(WorkUtils.ReadHTTPStreamContent(response.GetResponseStream()), state.Value);
            if (result != null)
            {
                string protocol;
                string address;
                int port;
                if (WorkUtils.ParseProtocol(state.Value.Key.Uri, out protocol, out address, out port))
                {
                    if (port == 0)
                        port = 80;
                    if (!string.IsNullOrEmpty(address) && port > 0)
                        WorkUtils.SendHTTPRequest(string.Format("http://{0}:{1}", address, port), state.Value, result, state.Value.Key.TimeOut, OnHTTPGetResult, worker);
                }
            }
        }

        public override void Match(List<BasicFileInfo> Files, FileURI FileAccess, string Keyword, SearchTypes SearchType, MatchTypes MatchType, SynchronizedDictionary<string, MatchFileItem> Result)
        {
            files = Files;
            fileAccess = FileAccess;
            keyword = Keyword;
            searchType = SearchType;
            matchType = MatchType;
            matchResult = Result;
            SendRequest(fileAccess, BuildMatchCommand(), OnHTTPGetResult);
        }

        public override void Delete(List<BasicFileInfo> Files, FileURI FileAccess, SynchronizedDictionary<string, BasicFileInfo> Result)
        {
            files = Files;
            fileAccess = FileAccess;
            deleteResult = Result;
            SendRequest(fileAccess, BuildDeleteCommand(), OnHTTPGetResult);
        }

        private void OnHTTPGetResult(IAsyncResult ar)
        {
            KeyValuePair<HttpWebRequest, KeyValuePair<FileURI, ManualResetEvent>> state = (KeyValuePair<HttpWebRequest, KeyValuePair<FileURI, ManualResetEvent>>)ar.AsyncState;
            HttpWebResponse response = (HttpWebResponse)state.Key.EndGetResponse(ar);
            ProcessRequest(WorkUtils.ReadHTTPStreamContent(response.GetResponseStream()), state.Value);
        }

        public override void Transfer(FileURI Source, FileURI Target, string[] IncludesFileTypes, TransferMethods TransferMethod, SearchTypes SearchType, OverwriteModes OverwriteMode, SynchronizedDictionary<string, KeyValuePair<BasicFileInfo, BasicFileInfo>> TransferedFiles)
        {
            throw new NotImplementedException();
        }
    }

    internal class FTPFileWorkProvider : BaseWorkProvider
    {
        private NetworkCredential credentials = null;

        internal FTPFileWorkProvider(BaseWork Worker, Dictionary<string, object> Parameters)
            : base(Worker, Parameters)
        {
        }

        public override string Name
        {
            get { return "FTP File"; }
        }

        public override string Description
        {
            get { return "FTP client"; }
        }

        public override string ImageFile
        {
            get { return "/Images/Publish2.png"; }
        }

        public override List<string> SupportedProtocols
        {
            get { return new List<string>(new string[] { "ftp" }); }
        }

        public override void Cancel(FileURI[] FilePaths)
        {
            IsCancelled = true;
        }

        public override void Find(FileURI FilePath, string[] IncludesFileTypes, string Keyword, SearchTypes SearchType, MatchTypes MatchType, HashMethods HashMethod, SynchronizedDictionary<string, List<BasicFileInfo>> SameFiles)
        {
            base.Find(FilePath, IncludesFileTypes, Keyword, SearchType, MatchType, HashMethod, SameFiles);
            credentials = new NetworkCredential(filePath.UserName, filePath.Password);
            Find();
        }

        private void Find()
        {
            string protocol;
            string address;
            int port;
            string path;
            if (WorkUtils.ParseProtocol(filePath.Uri, out protocol, out address, out port, out path))
                Find(filePath.Uri);
        }

        private string SendRequest(string FullPath, string Command)
        {
            FtpWebRequest request = (FtpWebRequest)FtpWebRequest.Create(FullPath);
            //work around for infamous .net ftp server 500 error: unrecognized command
            if (credentials == null)
                credentials = new NetworkCredential(filePath.UserName, filePath.Password);
            request.Credentials = credentials;
            request.Method = Command;
            if (filePath.TimeOut != 0)
            {
                request.ReadWriteTimeout = filePath.TimeOut * 1000;
                request.Timeout = filePath.TimeOut * 1000;
            }
            if (WorkSettings.Instance.Proxy != null)
                request.Proxy = WorkSettings.Instance.Proxy;
            string parameter = filePath.GetParameter(ParameterEncoding);
            Encoding encoding;
            if (!string.IsNullOrEmpty(parameter))
                encoding = Encoding.GetEncoding(parameter);
            else
                encoding = Encoding.UTF8;
            return WorkUtils.ReadHTTPStreamText(request.GetResponse().GetResponseStream(), false, encoding);
        }

        private void Find(string FullPath)
        {
            string result = SendRequest(FullPath, WebRequestMethods.Ftp.ListDirectoryDetails);
            List<FTPDirectoryListItem> paths = FTPDirectoryListParser.GetDirectoryList(result);
            if (paths != null)
            {
                foreach (FTPDirectoryListItem item in paths)
                {
                    if (IsCancelled)
                        break;
                    string path = WorkUtils.GetFullURL(FullPath) + item.Name;
                    if (item.IsDirectory)
                        Find(path + "/");
                    else
                    {
                        bool matchFileType;
                        bool hasFileType;
                        if (MatchFile(path, out matchFileType, out hasFileType))
                        {
                            string key = GetInfoKey(item, searchType);
                            if (!findResult.ContainsKey(key))
                                findResult.Add(key, new List<BasicFileInfo>());
                            findResult[key].Add(new BasicFileInfo(path, item.Length, item.CreationTime, item.CreationTime, FileAttributes.Normal));
                        }
                    }
                }
            }
            else
                WorkUtils.AddVerbose(string.Format("could not list ftp directory at: {0}", FullPath));
        }

        private string GetInfoKey(FTPDirectoryListItem Item, SearchTypes SearchType)
        {
            string key = string.Empty;
            if ((SearchType & SearchTypes.Size) == SearchTypes.Size)
                key += WorkUtils.PathSeparator + Item.Length.ToString();
            if ((SearchType & SearchTypes.Name) == SearchTypes.Name)
                key += WorkUtils.PathSeparator + Item.Name.ToLower();
            if ((SearchType & SearchTypes.CreatedDate) == SearchTypes.CreatedDate)
                key += WorkUtils.PathSeparator + Item.CreationTime.ToString();
            if ((SearchType & SearchTypes.ModifiedDate) == SearchTypes.ModifiedDate)
                key += WorkUtils.PathSeparator + Item.CreationTime.ToString();//yes...
            if ((SearchType & SearchTypes.Attributes) == SearchTypes.Attributes)
                key += WorkUtils.PathSeparator + Item.Flags;
            if (string.IsNullOrEmpty(key))
                key = WorkUtils.DefaultFileKey;
            return key;
        }

        public override void Match(List<BasicFileInfo> Files, FileURI FileAccess, string Keyword, SearchTypes SearchType, MatchTypes MatchType, SynchronizedDictionary<string, MatchFileItem> Result)
        {
            //because we should NOT download the files to work with the content, so, we just return what we found...
            files = Files;
            fileAccess = FileAccess;
            keyword = Keyword;
            searchType = SearchType;
            matchType = MatchType;
            matchResult = Result;
            if (!Result.ContainsKey(fileAccess.Uri.ToLower()))
                Result.Add(fileAccess.Uri.ToLower(), new MatchFileItem());
            foreach (BasicFileInfo file in files)
            {
                MatchCount++;
                Result[fileAccess.Uri.ToLower()].MatchFiles.Add(file);
            }
        }

        private void Delete()
        {
            foreach (BasicFileInfo item in files)
            {
                if (IsCancelled)
                    break;
                try
                {
                    SendRequest(item.FileName, WebRequestMethods.Ftp.DeleteFile);
                    deleteResult.Add(item.FileName, item);
                }
                catch (Exception ex)
                {
                    worker.OnProgress(WorkProgressTypes.FileAccess | WorkProgressTypes.Error, ex.ToString());
                }
            }
        }

        public override void Delete(List<BasicFileInfo> Files, FileURI FileAccess, SynchronizedDictionary<string, BasicFileInfo> Result)
        {
            files = Files;
            fileAccess = FileAccess;
            deleteResult = Result;
            Delete();
        }

        public override void Transfer(FileURI Source, FileURI Target, string[] IncludesFileTypes, TransferMethods TransferMethod, SearchTypes SearchType, OverwriteModes OverwriteMode, SynchronizedDictionary<string, KeyValuePair<BasicFileInfo, BasicFileInfo>> TransferedFiles)
        {
            throw new NotImplementedException();
        }
    }

    internal class LocalFileWorkProvider : BaseWorkProvider
    {
        internal LocalFileWorkProvider(BaseWork Worker)
            : base(Worker, null)
        {
        }

        public override string Name
        {
            get { return "Local File"; }
        }

        public override string Description
        {
            get { return "Local File"; }
        }

        public override string ImageFile
        {
            get { return "/Images/Server2.png"; }
        }

        public override List<string> SupportedProtocols
        {
            get { return new List<string>(); }
        }

        public override void Cancel(FileURI[] FilePaths)
        {
            IsCancelled = true;
        }

        private void Find(string FilePath)
        {
            string[] paths = FilePath.Split(new string[] { WorkUtils.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
#if(NTFS)
            List<string> ntfsDrives = new List<string>();
            bool useNTFS = WorkSettings.Instance.EnableNTFS;
            List<string> remainsPaths = new List<string>();
            if (useNTFS)
            {
                List<string> drives = new List<string>();
                foreach (string path in paths)
                {
                    string realPath = Path.GetFullPath(path).ToLower();
                    if (Path.GetPathRoot(realPath) == WorkUtils.GetFullPath(realPath))
                    {
                        if (!drives.Contains(realPath))
                            drives.Add(realPath);
                    }
                    else
                        remainsPaths.Add(realPath);
                }
                foreach (string drive in drives)
                {
                    DriveInfo info = new DriveInfo(drive);
                    if (info.DriveType == DriveType.Fixed && info.IsReady && info.DriveFormat.ToUpper() == "NTFS")
                    {
                        ntfsDrives.Add(drive);
                    }
                }
                useNTFS = ntfsDrives.Count > 0;
            }
            if (useNTFS)
            {
                foreach (string drive in ntfsDrives)
                {
                    string[] items = LocalNTFSStorage.Instance.GetFiles(drive);
                    if (items != null)
                        MatchFiles(items);
                    else
                        remainsPaths.Add(drive);
                }
                paths = remainsPaths.ToArray();
#endif
            }

            foreach (string path in paths)
            {
                if (IsCancelled)
                    break;
                if (!string.IsNullOrEmpty(path) && Directory.Exists(path))
                {
                    //mmm... infamous user access prohibited by windows, such as "System Volume Information"
                    //refer to workitem: http://filio.codeplex.com/workitem/28298
                    try
                    {
                        string[] items = Directory.GetDirectories(path);
                        foreach (string folder in items)
                        {
                            Find(folder);
                        }
                        items = Directory.GetFiles(path);
                        MatchFiles(items);
                    }
                    catch (Exception ex)
                    {
                        WorkUtils.AddVerbose(string.Format("could not access {0}: {1}", path, ex.ToString()));
                    }
                }
            }
        }

        public override void Find(FileURI FilePath, string[] IncludesFileTypes, string Keyword, SearchTypes SearchType, MatchTypes MatchType, HashMethods HashMethod, SynchronizedDictionary<string, List<BasicFileInfo>> SameFiles)
        {
            base.Find(FilePath, IncludesFileTypes, Keyword, SearchType, MatchType, HashMethod, SameFiles);
            Find(filePath.Uri);
        }

        private void Match()
        {
            foreach (BasicFileInfo file in files)
            {
                bool add;
                string key;
                if (IsCancelled)
                    break;
                switch (matchType)
                {
                    case MatchTypes.ContentSame:
                        key = WorkUtils.HashMD5FileEx(file.FileName, file.FileSize);
                        add = true;
                        break;
                    case MatchTypes.ContentContains:
                        //quick & dirty...
                        add = File.ReadAllText(file.FileName).IndexOf(keyword, StringComparison.InvariantCultureIgnoreCase) != -1;
                        key = keyword;
                        break;
                    case MatchTypes.ContentExtract:
                        string response;
                        add = LocalFileContentIndexStorage.Instance.Query(file.FileName, file.FileSize, keyword, out response);
                        key = keyword;
                        break;
                    default:
                        key = string.Empty;
                        add = false;
                        break;
                }
                if (add)
                {
                    MatchCount++;
                    if (!matchResult.ContainsKey(key))
                        matchResult.Add(key, new MatchFileItem());
                    MatchFileItem matchFile = matchResult[key];
                    matchFile.MatchFiles.Add(file);
                    if (matchFile.MatchFiles.Count > 1)
                        worker.OnProgress(key, matchFile.MatchFiles);
                }
            }
        }

        public override void Match(List<BasicFileInfo> Files, FileURI FileAccess, string Keyword, SearchTypes SearchType, MatchTypes MatchType, SynchronizedDictionary<string, MatchFileItem> Result)
        {
            files = Files;
            fileAccess = FileAccess;
            keyword = Keyword;
            searchType = SearchType;
            matchType = MatchType;
            matchResult = Result;
            Match();
        }

        private void Delete()
        {
            foreach (BasicFileInfo item in files)
            {
                if (File.Exists(item.FileName))
                {
                    try
                    {
                        if (IsCancelled)
                            break;
#if (!DEBUG)
                        File.Delete(item.FileName);
                        deleteResult.Add(item.FileName, item);
#endif
                    }
                    catch (Exception ex)
                    {
                        worker.OnProgress(WorkProgressTypes.FileAccess | WorkProgressTypes.Error, ex.ToString());
                    }
                }
            }
        }

        public override void Delete(List<BasicFileInfo> Files, FileURI FileAccess, SynchronizedDictionary<string, BasicFileInfo> Result)
        {
            files = Files;
            fileAccess = FileAccess;
            deleteResult = Result;
            Delete();
        }

        public override void Transfer(FileURI Source, FileURI Target, string[] IncludesFileTypes, TransferMethods TransferMethod, SearchTypes SearchType, OverwriteModes OverwriteMode, SynchronizedDictionary<string, KeyValuePair<BasicFileInfo, BasicFileInfo>> TransferedFiles)
        {
            transferedResult = TransferedFiles;
            includesFileTypes = IncludesFileTypes;
            searchType = SearchType;
            transferMethod = TransferMethod;
            overwriteMode = OverwriteMode;
            source = Source;
            target = Target;
            string protocol;
            string address;
            int port;
            string relativePath;
            if (WorkUtils.ParseProtocol(Target.Uri, out protocol, out address, out port, out relativePath))
            {
                if (protocol == WorkUtils.ProtocolLocal)
                    Transfer();
                else
                {
                    BaseWorkProvider provider = WorkUtils.GetWorkProvider(Target, worker, WorkSettings.Instance.Parameters);
                    if (provider != null)
                        provider.Transfer(Target, Source, includesFileTypes, transferMethod, searchType, overwriteMode, TransferedFiles);
                }
            }
            else
                worker.OnEmptyProtocol();
        }

        private void Transfer()
        {
            SynchronizedDictionary<string, BasicFileInfo> sourceFiles = new SynchronizedDictionary<string, BasicFileInfo>();
            SynchronizedDictionary<string, List<BasicFileInfo>> files = new SynchronizedDictionary<string, List<BasicFileInfo>>();
            Find(source, includesFileTypes, string.Empty, SearchTypes.None, MatchTypes.InfoSame, WorkSettings.Instance.HashMethod, files);
            foreach (string key in files.Keys)
            {
                foreach (BasicFileInfo file in files[key])
                {
                    FileInfo info = new FileInfo(file.FileName);
                    string item = file.FileName.Substring(source.Uri.Length).ToLower() + WorkUtils.PipeSeparator + info.Length + WorkUtils.PipeSeparator + info.LastWriteTime;
                    sourceFiles.Add(item, new BasicFileInfo(file.FileName.Substring(source.Uri.Length), info.Length, info.CreationTime, info.LastWriteTime, info.Attributes));
                }
            }

            SynchronizedDictionary<string, BasicFileInfo> targetFiles = new SynchronizedDictionary<string, BasicFileInfo>();
            files = new SynchronizedDictionary<string, List<BasicFileInfo>>();
            Find(target, includesFileTypes, string.Empty, SearchTypes.None, MatchTypes.InfoSame, WorkSettings.Instance.HashMethod, files);
            foreach (string key in files.Keys)
            {
                foreach (BasicFileInfo file in files[key])
                {
                    FileInfo info = new FileInfo(file.FileName);
                    string item = file.FileName.Substring(target.Uri.Length).ToLower() + WorkUtils.PipeSeparator + info.Length + WorkUtils.PipeSeparator + info.LastWriteTime;
                    targetFiles.Add(item, new BasicFileInfo(file.FileName.Substring(target.Uri.Length), info.Length, info.CreationTime, info.LastWriteTime, info.Attributes));
                }
            }

            files.Clear();

            Dictionary<string, BasicFileInfo> sourceFilesEx = new Dictionary<string, BasicFileInfo>();
            Dictionary<string, BasicFileInfo> targetFilesEx = new Dictionary<string, BasicFileInfo>();
            foreach (string key in sourceFiles.Keys)
            {
                if (!targetFiles.ContainsKey(key))
                    sourceFilesEx.Add(key, sourceFiles[key]);
            }
            foreach (string key in targetFiles.Keys)
            {
                if (!sourceFiles.ContainsKey(key))
                    targetFilesEx.Add(key, targetFiles[key]);
            }

            sourceFiles.Clear();
            targetFiles.Clear();

            foreach (KeyValuePair<string, BasicFileInfo> item in sourceFilesEx)
            {
                if (IsCancelled)
                    break;
                switch (transferMethod)
                {
                    case TransferMethods.Download:
                    case TransferMethods.Synchronize:
                        TransferFile(source, target, item.Value, searchType, overwriteMode);
                        break;
                    default:
                        break;
                }
            }
            sourceFilesEx.Clear();

            foreach (KeyValuePair<string, BasicFileInfo> item in targetFilesEx)
            {
                if (IsCancelled)
                    break;
                switch (transferMethod)
                {
                    case TransferMethods.Upload:
                    case TransferMethods.Synchronize:
                        TransferFile(target, source, item.Value, searchType, overwriteMode);
                        break;
                    default:
                        break;
                }
            }
            targetFilesEx.Clear();
        }

        private void TransferFile(FileURI Source, FileURI Target, BasicFileInfo Item, SearchTypes SearchType, OverwriteModes OverwriteMode)
        {
            string targetFile = WorkUtils.GetFullPath(Target.Uri) + Item.FileName;
            string sourceFile = WorkUtils.GetFullPath(Source.Uri) + Item.FileName;
            bool write;
            bool onlyInfo = false;
            FileInfo info = null;
            if (File.Exists(targetFile))
            {
                info = new FileInfo(targetFile);

                if (Item.FileSize == info.Length)
                {
                    string sourceKey = WorkUtils.HashMD5FileEx(sourceFile, Item.FileSize);
                    string targetKey = WorkUtils.HashMD5FileEx(targetFile, Item.FileSize);
                    if (sourceKey == targetKey)
                        onlyInfo = true;
                }
                if (onlyInfo && OverwriteMode == OverwriteModes.None)
                    write = false;
                else
                {
                    write = true;
                    if (Item.FileSize != info.Length || Item.LastModifiedDate != info.LastWriteTime)
                    {
                        if ((OverwriteMode & OverwriteModes.Larger) == OverwriteModes.Larger)
                            write = Item.FileSize > info.Length;
                        if (write || !onlyInfo)
                        {
                            if ((OverwriteMode & OverwriteModes.Newer) == OverwriteModes.Newer)
                                write = Item.LastModifiedDate > info.LastWriteTime;
                        }
                        if (write)
                        {
                            if ((OverwriteMode & OverwriteModes.Smaller) == OverwriteModes.Smaller)
                                write = Item.FileSize < info.Length;
                        }
                        if (write)
                        {
                            if ((OverwriteMode & OverwriteModes.Older) == OverwriteModes.Older)
                                write = Item.LastModifiedDate < info.LastWriteTime;
                        }
                    }
                    else
                        write = false;
                }
            }
            else
                write = true;
            if (write)
            {
                string originalFile = Item.FileName;
                Item.FileName = sourceFile;
                try
                {
                    BasicFileInfo newItem = new BasicFileInfo(targetFile, Item.FileSize, Item.CreatedDate, Item.LastModifiedDate, Item.Attributes);
                    string path = Path.GetDirectoryName(targetFile);
                    if (!Directory.Exists(path))
                        Directory.CreateDirectory(path);
                    if (onlyInfo)
                    {
                        if ((SearchType & SearchTypes.Attributes) == SearchTypes.Attributes && info.Attributes != Item.Attributes)
                            File.SetAttributes(targetFile, Item.Attributes);
                        if ((SearchType & SearchTypes.CreatedDate) == SearchTypes.CreatedDate && info.CreationTime != Item.CreatedDate)
                            File.SetCreationTime(targetFile, Item.CreatedDate);
                        if ((SearchType & SearchTypes.ModifiedDate) == SearchTypes.ModifiedDate && info.LastWriteTime != Item.LastModifiedDate)
                            File.SetLastWriteTime(targetFile, Item.LastModifiedDate);
                        if ((SearchType & SearchTypes.Name) == SearchTypes.Name && originalFile != info.Name)//rename
                            File.Move(targetFile, Target.Uri + originalFile);
                    }
                    else
                        File.Copy(sourceFile, targetFile, true);
                    transferedResult.Add(sourceFile, new KeyValuePair<BasicFileInfo, BasicFileInfo>(Item, newItem));
                }
                catch (Exception ex)
                {
                    worker.OnProgress(WorkProgressTypes.FileAccess | WorkProgressTypes.Error, ex.ToString());
                }
            }
        }
    }

    public interface ICancellable
    {
        void Cancel(FileURI[] FilePaths);
    }

    internal class WorkV7FindFileRunner : IParallelWorker, ICancellable
    {
        private FileURI[] filePaths;
        private BaseWork worker;
        private string[] includesFileTypes;
        private SearchTypes searchType;
        private string keyword;
        private MatchTypes matchType;
        private HashMethods hashMethod;
        private SynchronizedDictionary<string, List<BasicFileInfo>> sameFiles;
        private BaseWorkProvider currentProvider = null;

        public WorkV7FindFileRunner(FileURI[] FilePaths, BaseWork Worker, string[] IncludesFileTypes, string Keyword, SearchTypes SearchType, MatchTypes MatchType, HashMethods HashMethod, SynchronizedDictionary<string, List<BasicFileInfo>> SameFiles)
        {
            filePaths = FilePaths;
            worker = Worker;
            includesFileTypes = IncludesFileTypes;
            searchType = SearchType;
            keyword = Keyword;
            matchType = MatchType;
            hashMethod = HashMethod;
            sameFiles = SameFiles;
        }

        public void Cancel(FileURI[] FilePaths)
        {
            if (currentProvider != null)
            {
                foreach (FileURI item in FilePaths)
                {
                    if (item.Uri == currentProvider.FilePath.Uri)
                    {
                        currentProvider.Cancel(FilePaths);
                        break;
                    }
                }
            }
        }

        public void Run()
        {
            worker.OnProgress(WorkProgressTypes.Detail, "find file work runner start:" + DateTime.Now);
            foreach (FileURI path in filePaths)
            {
                currentProvider = WorkUtils.GetWorkProvider(path, worker, WorkSettings.Instance.Parameters);
                if (currentProvider != null)
                {
                    worker.OnProgress(WorkProgressTypes.Detail, string.Format("find file work {0} start:{1}", currentProvider.Name, DateTime.Now));
                    try
                    {
                        path.Status = FileUriStatus.Working;
                        currentProvider.Find(path, includesFileTypes, keyword, searchType, matchType, hashMethod, sameFiles);
                        path.Status = currentProvider.IsCancelled ? FileUriStatus.Cancelled : FileUriStatus.Finished;
                    }
                    catch (Exception ex)
                    {
                        path.Status = FileUriStatus.Failed;
                        worker.OnProgress(WorkProgressTypes.FileAccess, ex.ToString());
                    }
                    worker.OnProgress(WorkProgressTypes.Detail, string.Format("find file work {0} end:{1}", currentProvider.Name, DateTime.Now));
                }
            }
            worker.OnProgress(WorkProgressTypes.Detail, "find file work runner end:" + DateTime.Now);
        }
    }

    internal class WorkV7DeleteResultRunner : IParallelWorker, ICancellable
    {
        private List<BasicFileInfo> files;
        private FileURI fileAccess;
        private BaseWork worker;
        private SynchronizedDictionary<string, BasicFileInfo> result;
        private BaseWorkProvider currentProvider = null;

        public WorkV7DeleteResultRunner(List<BasicFileInfo> Files, FileURI FileAccess, BaseWork Worker, SynchronizedDictionary<string, BasicFileInfo> Result)
        {
            files = Files;
            fileAccess = FileAccess;
            worker = Worker;
            result = Result;
        }

        public void Cancel(FileURI[] FilePaths)
        {
            if (currentProvider != null)
            {
                foreach (FileURI item in FilePaths)
                {
                    if (item.Uri == currentProvider.FilePath.Uri)
                        currentProvider.Cancel(FilePaths);
                }
            }
        }

        public void Run()
        {
            worker.OnProgress(WorkProgressTypes.Detail, "delete result work runner start:" + DateTime.Now);
            if (files.Count > 0)
            {
                currentProvider = WorkUtils.GetWorkProvider(new FileURI(files[0].FileName), worker, WorkSettings.Instance.Parameters);
                if (currentProvider != null)
                {
                    try
                    {
                        currentProvider.Delete(files, fileAccess, result);
                    }
                    catch (Exception ex)
                    {
                        worker.OnProgress(WorkProgressTypes.FileAccess | WorkProgressTypes.Error, ex.ToString());
                    }
                }
            }
            worker.OnProgress(WorkProgressTypes.Detail, "delete result work runner end:" + DateTime.Now);
        }
    }

    internal class WorkV7TransferRunner : IParallelWorker, ICancellable
    {
        private FileURI source;
        private FileURI target;
        private string[] includesFileTypes;
        private TransferMethods transferMethod;
        private SearchTypes searchType;
        private OverwriteModes overwriteMode;
        private BaseWork worker;
        private SynchronizedDictionary<string, KeyValuePair<BasicFileInfo, BasicFileInfo>> result;
        private BaseWorkProvider currentProvider = null;

        public WorkV7TransferRunner(FileURI Source, FileURI Target, string[] IncludesFileTypes, TransferMethods TransferMethod, SearchTypes SearchType, OverwriteModes OverwriteMode, BaseWork Worker, SynchronizedDictionary<string, KeyValuePair<BasicFileInfo, BasicFileInfo>> Result)
        {
            source = Source;
            target = Target;
            includesFileTypes = IncludesFileTypes;
            transferMethod = TransferMethod;
            searchType = SearchType;
            overwriteMode = OverwriteMode;
            worker = Worker;
            result = Result;
        }

        public void Cancel(FileURI[] FilePaths)
        {
            if (currentProvider != null)
            {
                foreach (FileURI item in FilePaths)
                {
                    if (item.Uri == currentProvider.FilePath.Uri)
                        currentProvider.Cancel(FilePaths);
                }
            }
        }

        public void Run()
        {
            worker.OnProgress(WorkProgressTypes.Detail, "transfer result work runner start:" + DateTime.Now);
            if (source != null && target != null)
            {
                currentProvider = WorkUtils.GetWorkProvider(source, worker, WorkSettings.Instance.Parameters);
                if (currentProvider != null)
                {
                    try
                    {
                        source.Status = FileUriStatus.Working;
                        target.Status = FileUriStatus.Working;
                        currentProvider.Transfer(source, target, includesFileTypes, transferMethod, searchType, overwriteMode, result);
                        source.Status = currentProvider.IsCancelled ? FileUriStatus.Cancelled : FileUriStatus.Finished;
                        target.Status = source.Status;
                    }
                    catch (Exception ex)
                    {
                        source.Status = FileUriStatus.Failed;
                        target.Status = FileUriStatus.Failed;
                        worker.OnProgress(WorkProgressTypes.FileAccess | WorkProgressTypes.Error, ex.ToString());
                    }
                }
            }
            worker.OnProgress(WorkProgressTypes.Detail, "transfer result work runner end:" + DateTime.Now);
        }
    }

    internal class WorkV7FindResultRunner : IParallelWorker, ICancellable
    {
        private List<BasicFileInfo> files;
        private FileURI fileAccess;
        private string keyword;
        private SearchTypes searchType;
        private MatchTypes matchType;
        private BaseWork worker;
        private SynchronizedDictionary<string, MatchFileItem> result;
        private BaseWorkProvider currentProvider = null;

        public WorkV7FindResultRunner(List<BasicFileInfo> Files, FileURI FileAccess, string Keyword, SearchTypes SearchType, MatchTypes MatchType, BaseWork Worker, SynchronizedDictionary<string, MatchFileItem> Result)
        {
            files = Files;
            fileAccess = FileAccess;
            keyword = Keyword;
            searchType = SearchType;
            matchType = MatchType;
            worker = Worker;
            result = Result;
        }

        public void Cancel(FileURI[] FilePaths)
        {
            if (currentProvider != null)
            {
                foreach (FileURI item in FilePaths)
                {
                    if (item.Uri == currentProvider.FilePath.Uri)
                        currentProvider.Cancel(FilePaths);
                }
            }
        }

        private int matchCount = 0;
        public int MatchCount
        {
            get { return matchCount; }
        }

        public void Run()
        {
            worker.OnProgress(WorkProgressTypes.Detail, "find result work runner start:" + DateTime.Now);
            if (files.Count > 0)
            {
                currentProvider = WorkUtils.GetWorkProvider(new FileURI(files[0].FileName), worker, WorkSettings.Instance.Parameters);
                if (currentProvider != null)
                {
                    try
                    {
                        int count = result.Count;
                        currentProvider.Match(files, fileAccess, keyword, searchType, matchType, result);
                        matchCount = result.Count - count;
                    }
                    catch (Exception ex)
                    {
                        worker.OnProgress(WorkProgressTypes.FileAccess | WorkProgressTypes.Error, ex.ToString());
                    }
                }
            }
            worker.OnProgress(WorkProgressTypes.Detail, "find result work runner end:" + DateTime.Now + " matching " + matchCount);
        }
    }

    [Flags]
    public enum UserRights
    {
        Forbidden = 0,
        Discover = 1,
        Search = 2,
        Delete = 4,
        Upload = 8,
        Download = 16
    }

    public class RoleManager
    {
        private Dictionary<string, Role> roles = new Dictionary<string, Role>();
        public void AddRole(Role Role)
        {
            if (!roles.ContainsKey(Role.Name.ToLower()))
                roles.Add(Role.Name.ToLower(), Role);
        }

        public void Clear()
        {
            roles.Clear();
        }

        public List<string> GetFilePaths(UserAuth User, UserRights Right)
        {
            List<string> files = new List<string>();
            foreach (KeyValuePair<string, Role> role in roles)
            {
                if (role.Value.FindRight(User, string.Empty, Right))
                {
                    foreach (FileURI file in role.Value.FilePaths)
                    {
                        files.Add(file.Uri);
                    }
                }
            }
            return files;
        }

        public bool Verify(UserAuth User, string FilePath, UserRights Right)
        {
            bool found = false;
            foreach (KeyValuePair<string, Role> role in roles)
            {
                if (role.Value.FindRight(User, FilePath, Right))
                {
                    found = true;
                    break;
                }
            }
            return found;
        }
    }

    [Serializable]
    public class Role
    {
        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public Role()
        {
        }

        public Role(string Name)
        {
            name = Name;
        }

        private Dictionary<string, UserAuth> users = new Dictionary<string, UserAuth>();
        private List<UserAuth> allUsers = new List<UserAuth>();
        public List<UserAuth> AllUsers
        {
            get { return allUsers; }
            set
            {
                allUsers = value;
                foreach (UserAuth item in allUsers)
                {
                    if (!users.ContainsKey(item.UserName.ToLower()))
                        users.Add(item.UserName.ToLower(), item);
                }
            }
        }

        public void AddUser(UserAuth User)
        {
            if (!users.ContainsKey(User.UserName.ToLower()))
                users.Add(User.UserName.ToLower(), User);
        }

        private List<FileURI> filePaths = new List<FileURI>();
        public List<FileURI> FilePaths
        {
            get { return filePaths; }
            set { filePaths = value; }
        }

        public void AddFilePath(FileURI FilePath)
        {
            string protocol;
            string address;
            int port;
            string relativePath;
            if (WorkUtils.ParseProtocol(FilePath.Uri, out protocol, out address, out port, out relativePath))
            {
                if (protocol == WorkUtils.ProtocolLocal)
                    FilePath.Uri = WorkUtils.GetFullPath(FilePath.Uri).ToLower();
                else
                    FilePath.Uri = FilePath.Uri.ToLower();
                bool found = false; ;
                foreach (FileURI item in filePaths)
                {
                    if (item.Uri == FilePath.Uri.ToLower())
                    {
                        found = true;
                        break;
                    }
                }
                if (!found)
                    filePaths.Add(FilePath);
            }
        }

        public void AddFilePath(string FilePath)
        {
            AddFilePath(new FileURI(FilePath));
        }

        private List<UserRights> rights = new List<UserRights>();
        public List<UserRights> Rights
        {
            get { return rights; }
            set { rights = value; }
        }

        internal bool FindRight(UserAuth User, string FilePath, UserRights Right)
        {
            bool found = false;
            foreach (KeyValuePair<string, UserAuth> user in users)
            {
                if (user.Key == User.UserName.ToLower()
                    && user.Value.Password == User.Password)
                {
                    found = true;
                    break;
                }
            }
            if (found && !string.IsNullOrEmpty(FilePath))
            {
                found = false;
                FilePath = WorkUtils.GetFullPath(FilePath).ToLower();
                foreach (FileURI item in filePaths)
                {
                    if (WorkUtils.MatchExact(FilePath, item.Uri))
                    {
                        found = true;
                        break;
                    }
                }
            }
            if (found)
                found = rights.Contains(Right);
            return found;
        }

        public void AddRight(UserRights Right)
        {
            if (!rights.Contains(Right))
                rights.Add(Right);
        }
    }

    public abstract class BaseManager
    {
        private RoleManager roleManager = new RoleManager();

        public event EventHandler<ProgressEventArgs> Progress;
        public event EventHandler<DataTransferEventArgs> Request;

        public RoleManager Roles
        {
            get { return roleManager; }
        }

        public abstract bool IsRunning { get; }

        public abstract void Start(int Port);

        public abstract void Close();

        protected internal void OnProgress(WorkProgressTypes ProgressType, string Status)
        {
            if (Progress != null)
                Progress(this, new ProgressEventArgs(ProgressType, Status));
        }

        protected internal void OnRequest(DataTransferEventArgs e)
        {
            if (Request != null)
                Request(this, e);
        }

        private bool ValidateUser(Dictionary<string, string> Parameters, UserRights UserRight, out UserAuth User)
        {
            User = new UserAuth();
            User.UserName = string.Empty;
            User.Password = string.Empty;
            if (Parameters.ContainsKey(RemoteCommander.ParameterUserName))
                User.UserName = Parameters[RemoteCommander.ParameterUserName];
            if (Parameters.ContainsKey(RemoteCommander.ParameterPassword))
                User.Password = Parameters[RemoteCommander.ParameterPassword];
            return roleManager.Verify(User, string.Empty, UserRight);
        }

        public virtual byte[] ProcessRequest(byte[] Content)
        {
            byte[] response = null;
            Dictionary<string, string> parameters;
            UserAuth user;
            if (RemoteCommander.ParseCommand(Content, out parameters))
            {
                switch (parameters[RemoteCommander.CommandPrefix])
                {
                    case RemoteCommander.CommandDelete:
                        if (ValidateUser(parameters, UserRights.Delete, out user))
                        {
                            string[] filePaths = parameters[RemoteCommander.ParameterFilePaths].Split(new string[] { RemoteCommander.FileItemSeparator }, StringSplitOptions.RemoveEmptyEntries);
                            StringBuilder result = new StringBuilder();
                            result.Append(RemoteCommander.ParameterFilePaths + RemoteCommander.KeyValueSeparator);
                            foreach (string file in filePaths)
                            {
                                string protocol;
                                string address;
                                int port;
                                string relativePath;
                                if (WorkUtils.ParseProtocol(file, out protocol, out address, out port, out relativePath))
                                {
                                    string[] parts = relativePath.Split(new string[] { WorkUtils.PathSeparator }, StringSplitOptions.None);
                                    string item = parts[0];
                                    if (File.Exists(item))
                                    {
#if(!DEBUG)
                                        File.Delete(item);
                                        result.Append(file + RemoteCommander.FileItemSeparator);
#endif
                                    }
                                }
                            }
                            response = RemoteCommander.BuildCommand(RemoteCommander.CommandDelete, result.ToString());
                        }
                        else
                            response = RemoteCommander.BuildCommand(RemoteCommander.CommandAccessDenied, string.Empty);
                        break;
                    case RemoteCommander.CommandDiscover:
                        if (ValidateUser(parameters, UserRights.Discover, out user))
                        {
                            StringBuilder result = new StringBuilder();
                            result.Append(RemoteCommander.ParameterFilePaths + RemoteCommander.KeyValueSeparator);
                            List<string> allowedPaths = roleManager.GetFilePaths(user, UserRights.Discover);
                            foreach (string item in allowedPaths)
                            {
                                result.Append(item + RemoteCommander.FileItemSeparator);
                            }
                            response = RemoteCommander.BuildCommand(RemoteCommander.CommandDiscover, result.ToString());
                        }
                        else
                            response = RemoteCommander.BuildCommand(RemoteCommander.CommandAccessDenied, string.Empty);
                        break;
                    case RemoteCommander.CommandQuery:
                        if (ValidateUser(parameters, UserRights.Search, out user))
                        {
                            if (parameters.ContainsKey(RemoteCommander.ParameterFilePaths))
                            {
                                WorkV7 work = new WorkV7();
                                work.WorkMode = WorkModes.Server;
                                string[] filePaths = parameters[RemoteCommander.ParameterFilePaths].Split(new string[] { WorkUtils.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
                                string[] includeFileTypes = parameters[RemoteCommander.ParameterIncludeFileTypes].Split(new string[] { WorkUtils.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
                                SearchTypes searchType = (SearchTypes)Convert.ToInt32(parameters[RemoteCommander.ParameterSearchType]);
                                string keyword = parameters[RemoteCommander.ParameterKeyword];
                                MatchTypes matchType = (MatchTypes)Convert.ToInt32(parameters[RemoteCommander.ParameterMatchType]);
                                HashMethods hashMethod = (HashMethods)Convert.ToInt32(parameters[RemoteCommander.ParameterHashMethod]);
                                List<string> filteredFilePaths = new List<string>();
                                foreach (string file in filePaths)
                                {
                                    if (roleManager.Verify(user, file, UserRights.Search))
                                        filteredFilePaths.Add(file);
                                }
                                filePaths = filteredFilePaths.ToArray();
                                Dictionary<string, MatchFileItem> matchFiles = work.Find(filePaths, includeFileTypes, keyword, searchType, matchType, hashMethod);
                                StringBuilder result = new StringBuilder();
                                result.Append(RemoteCommander.ParameterFilePaths + RemoteCommander.KeyValueSeparator);
                                //todo:might be huge result
                                foreach (KeyValuePair<string, MatchFileItem> item in matchFiles)
                                {
                                    result.Append(item.Key + RemoteCommander.FileItemSeparator);
                                    foreach (BasicFileInfo file in item.Value.MatchFiles)
                                    {
                                        result.Append(file.FileName + WorkUtils.PathSeparator + file.FileSize + WorkUtils.PathSeparator + file.CreatedDate + WorkUtils.PathSeparator + file.LastModifiedDate + WorkUtils.PathSeparator + file.Attributes + RemoteCommander.FileItemSeparator);
                                    }
                                    result.Append(RemoteCommander.FileGroupSeparator);
                                }
                                response = RemoteCommander.BuildCommand(RemoteCommander.CommandQuery, result.ToString());
                            }
                            else
                                response = RemoteCommander.BuildCommand(RemoteCommander.CommandNoData, string.Empty);
                        }
                        else
                            response = RemoteCommander.BuildCommand(RemoteCommander.CommandAccessDenied, string.Empty);
                        break;
                    case RemoteCommander.CommandMatch:
                        if (ValidateUser(parameters, UserRights.Search, out user))
                        {
                            if (parameters.ContainsKey(RemoteCommander.ParameterFilePaths))
                            {
                                SearchTypes searchType = (SearchTypes)Convert.ToInt32(parameters[RemoteCommander.ParameterSearchType]);
                                string keyword = parameters[RemoteCommander.ParameterKeyword];
                                MatchTypes matchType = (MatchTypes)Convert.ToInt32(parameters[RemoteCommander.ParameterMatchType]);
                                string[] filePaths = parameters[RemoteCommander.ParameterFilePaths].Split(new string[] { RemoteCommander.FileItemSeparator }, StringSplitOptions.RemoveEmptyEntries);
                                WorkV7 work = new WorkV7();
                                work.WorkMode = WorkModes.Server;
                                SynchronizedDictionary<string, List<BasicFileInfo>> sameFiles = new SynchronizedDictionary<string, List<BasicFileInfo>>();
                                string key = "Default";
                                foreach (string file in filePaths)
                                {
                                    string protocol;
                                    string address;
                                    int port;
                                    string relativePath;
                                    if (WorkUtils.ParseProtocol(file, out protocol, out address, out port, out relativePath))
                                    {
                                        string[] parts = relativePath.Split(new string[] { WorkUtils.PathSeparator }, StringSplitOptions.None);
                                        string item = parts[0];
                                        key = address + port;
                                        if (!sameFiles.ContainsKey(key))
                                            sameFiles.Add(key, new List<BasicFileInfo>());
                                        sameFiles[key].Add(new BasicFileInfo(item, Convert.ToInt64(parts[1]), Convert.ToDateTime(parts[2]), Convert.ToDateTime(parts[3]), (FileAttributes)Convert.ToInt32(parts[4])));
                                    }
                                }
                                Dictionary<string, MatchFileItem> matchFiles = work.Match(sameFiles, null, keyword, searchType, matchType);

                                //Dictionary<string, List<string>> files = new Dictionary<string, List<string>>();
                                //foreach (KeyValuePair<string, MatchFileItem> item in matchFiles)
                                //{
                                //    if (!files.ContainsKey(item.Key))
                                //        files.Add(item.Key, new List<string>());
                                //    files[item.Key].AddRange(item.Value.MatchFiles);
                                //}
                                StringBuilder result = new StringBuilder();
                                result.Append(RemoteCommander.ParameterFilePaths + RemoteCommander.KeyValueSeparator);
                                //todo:might be huge result
                                foreach (KeyValuePair<string, MatchFileItem> item in matchFiles)
                                {
                                    result.Append(item.Key + RemoteCommander.FileItemSeparator);
                                    foreach (BasicFileInfo file in item.Value.MatchFiles)
                                    {
                                        result.Append(file.FileName + WorkUtils.PathSeparator + file.FileSize + WorkUtils.PathSeparator + file.CreatedDate + WorkUtils.PathSeparator + file.LastModifiedDate + WorkUtils.PathSeparator + file.Attributes + RemoteCommander.FileItemSeparator);
                                    }
                                    result.Append(RemoteCommander.FileGroupSeparator);
                                }
                                response = RemoteCommander.BuildCommand(RemoteCommander.CommandMatch, result.ToString());
                                LocalHashStorage.Instance.Flush();
                                LocalFileContentIndexStorage.Instance.Flush();
                                LocalNTFSStorage.Instance.Flush();
                            }
                            else
                                response = RemoteCommander.BuildCommand(RemoteCommander.CommandNoData, string.Empty);
                        }
                        else
                            response = RemoteCommander.BuildCommand(RemoteCommander.CommandAccessDenied, string.Empty);
                        break;
                    default:
                        response = RemoteCommander.BuildCommand(RemoteCommander.CommandNoCommand, string.Empty);
                        break;
                }
            }
            return response;
        }
    }

    /// <summary>
    /// accept access from remote computers to search file with http
    /// </summary>
    public class WorkV7HTTPManager : BaseManager
    {
        private HttpListener listener = null;
        private bool finished = false;

        public override bool IsRunning
        {
            get { return !finished; }
        }

        public override void Start(int Port)
        {
            if (HttpListener.IsSupported)
            {
                try
                {
                    listener = new HttpListener();
                    listener.Prefixes.Add(string.Format("http://*:{0}/", Port));
                    listener.Start();
                    OnProgress(WorkProgressTypes.Network, "listener started");
                    while (!finished)
                    {
                        HttpListenerContext context = listener.GetContext();
                        HttpListenerRequest request = context.Request;
                        byte[] buffer = WorkUtils.ReadHTTPStreamContent(request.InputStream);
                        DataTransferEventArgs e = new DataTransferEventArgs(null, buffer);
                        OnRequest(e);
                        switch (e.Cancel)
                        {
                            case CancelModes.None:
                                buffer = ProcessRequest(buffer);
                                using (Stream writer = context.Response.OutputStream)
                                {
                                    buffer = WorkUtils.CompressBuffer(buffer);
                                    writer.Write(buffer, 0, buffer.Length);
                                    writer.Write(WorkUtils.DataEndings, 0, WorkUtils.DataEndings.Length);
                                    writer.Flush();
                                }
                                context.Response.OutputStream.Close();
                                break;
                            case CancelModes.CancelRequest:
                                break;
                            case CancelModes.Shutdown:
                                finished = true;
                                break;
                            default:
                                break;
                        }
                    }
                    listener.Close();
                }
                catch (Exception ex)
                {
                    OnProgress(WorkProgressTypes.Network | WorkProgressTypes.Error, ex.ToString());
                }
            }
            else
                OnProgress(WorkProgressTypes.Network | WorkProgressTypes.Error, "HTTP Listener is not supported.");
        }

        ~WorkV7HTTPManager()
        {
            Close();
        }

        public override void Close()
        {
            finished = true;
            if (listener != null)
                listener.Abort();
            OnProgress(WorkProgressTypes.Network, "listener closed");
        }

    }

    /// <summary>
    /// accept access from remote computers to search file with tcp
    /// </summary>
    public class WorkV7TCPManager : BaseManager
    {
        private AsynchronousSocketListener listener = null;
        private bool finished = false;

        public override bool IsRunning
        {
            get { return !finished; }
        }

        public override void Start(int Port)
        {
            listener = new AsynchronousSocketListener();
            listener.Connected += new EventHandler<DataTransferEventArgs>(OnListenerConnected);
            listener.Received += new EventHandler<DataTransferEventArgs>(OnListenerReceived);
            listener.Sent += new EventHandler<DataTransferEventArgs>(OnListenerSent);
            listener.Failed += new EventHandler<DataTransferEventArgs>(OnListenerFailed);
            OnProgress(WorkProgressTypes.Network, "listener starting");
            listener.Start(Port);
        }

        ~WorkV7TCPManager()
        {
            Close();
        }

        public override void Close()
        {
            listener.Close();
            finished = true;
            OnProgress(WorkProgressTypes.Network, "listener closed");
        }

        private void OnListenerFailed(object sender, DataTransferEventArgs e)
        {
            OnProgress(WorkProgressTypes.Network | WorkProgressTypes.Error, "Failed:" + e.Content);
        }

        private void OnListenerSent(object sender, DataTransferEventArgs e)
        {
            e.Worker.Receive();
        }

        private void OnListenerReceived(object sender, DataTransferEventArgs e)
        {
            OnRequest(e);
            switch (e.Cancel)
            {
                case CancelModes.None:
                    e.Buffer = ProcessRequest(e.Buffer);
                    break;
                case CancelModes.CancelRequest:
                    break;
                case CancelModes.Shutdown:
                    listener.Close();
                    break;
                default:
                    break;
            }
        }

        private void OnListenerConnected(object sender, DataTransferEventArgs e)
        {
            OnProgress(WorkProgressTypes.Network, "client connected from:" + e.Content);
        }
    }

    /// <summary>
    /// multi-thread parallel computing distributed file management
    /// </summary>
    public class WorkV7 : BaseWork
    {
        private List<ICancellable> runners = new List<ICancellable>();

        public void Cancel(FileURI[] FilePaths)
        {
            foreach (ICancellable item in runners)
            {
                item.Cancel(FilePaths);
            }
        }

        public override Dictionary<string, MatchFileItem> Find(FileURI[] FilePaths, string[] IncludesFileTypes, string Keyword, SearchTypes SearchType, MatchTypes MatchType, HashMethods HashMethod)
        {
            SynchronizedDictionary<string, List<BasicFileInfo>> sameFiles = new SynchronizedDictionary<string, List<BasicFileInfo>>();
            List<WorkV7FindFileRunner> findFileRunners = new List<WorkV7FindFileRunner>();
            int step = FilePaths.Length / WorkSettings.Instance.MaxWorkers;
            if (step == 0)
                step = 1;
            if (FilePaths.Length % step != 0)
                step++;
            runners = new List<ICancellable>();
            for (int i = 0; i < FilePaths.Length; i += step)
            {
                FileURI[] parts = new FileURI[step];
                Array.Copy(FilePaths, i, parts, 0, step);
                WorkV7FindFileRunner runner = new WorkV7FindFileRunner(parts, this, IncludesFileTypes, Keyword, SearchType, MatchType, HashMethod, sameFiles);
                findFileRunners.Add(runner);
                runners.Add(runner);
            }
            ParallelProcessor.ExecuteParallel(findFileRunners.ToArray());
            return Match(sameFiles, FilePaths, Keyword, SearchType, MatchType);
        }

        public Dictionary<string, MatchFileItem> Match(SynchronizedDictionary<string, List<BasicFileInfo>> SameFiles, FileURI[] FilePaths, string Keyword, SearchTypes SearchType, MatchTypes MatchType)
        {
            Dictionary<string, MatchFileItem> result = new Dictionary<string, MatchFileItem>();
            if (MatchType != MatchTypes.InfoSame)
            {
                Dictionary<string, string> drivePartitionMappings = new Dictionary<string, string>();
                int matchCount = 0;
#if (DRIVEID)
                if (WorkSettings.Instance.EnableDriveIdentification)
                {
                    try
                    {
                        /*using (ManagementClass devs = new ManagementClass(@"Win32_Diskdrive"))
                        {
                            ManagementObjectCollection moc = devs.GetInstances();
                            foreach (ManagementObject mo in moc)
                            {
                                string drive = mo["DeviceId"].ToString();
                                foreach (ManagementObject b in mo.GetRelated("Win32_DiskPartition"))
                                {
                                    foreach (ManagementBaseObject c in b.GetRelated("Win32_LogicalDisk"))
                                    {
                                        string partition = c["Name"].ToString();
                                        drivePartitionMappings.Add(partition.ToLower(), drive.ToLower());
                                    }
                                }
                            }
                        }*/

                        //faster, but still slow, might take up to 10 seconds(I don't know what happended).
                        //I could use Win32 API to quickly get the result, but it is ideally NOT to use Win32 API by yourself...
                        ObjectQuery query = new ObjectQuery("select * from Win32_DiskPartition");
                        ManagementObjectSearcher searcher = new
                        ManagementObjectSearcher(query);
                        ManagementObjectCollection drives = searcher.Get();
                        foreach (ManagementObject current in drives)
                        {
                            string drive = current["deviceid"].ToString();
                            int index = drive.IndexOf(",");
                            if (index != -1)
                                drive = drive.Substring(0, index);
                            ObjectQuery associators = new ObjectQuery("ASSOCIATORS OF {Win32_DiskPartition.DeviceID=\"" + current["deviceid"] + "\"} where assocclass=Win32_LogicalDiskToPartition");
                            searcher = new ManagementObjectSearcher(associators);
                            ManagementObjectCollection disks = searcher.Get();
                            foreach (ManagementObject disk in disks)
                            {
                                string partition = disk["deviceid"].ToString();
                                drivePartitionMappings.Add(partition.ToLower() + @"\", drive.ToLower());
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        OnProgress(WorkProgressTypes.FileAccess | WorkProgressTypes.Error, "Could not get drive partition mappings through WMI:" + ex.ToString());
                    }
                }
#endif
                SynchronizedDictionary<string, List<BasicFileInfo>> sameGroupFiles = new SynchronizedDictionary<string, List<BasicFileInfo>>();
                foreach (List<BasicFileInfo> item in SameFiles.Values)
                {
                    if (item.Count > 1 || WorkMode == WorkModes.Server)
                    {
                        foreach (BasicFileInfo file in item)
                        {
                            string partition;
                            string protocol;
                            string address;
                            int port;
                            if (WorkUtils.ParseProtocol(file.FileName, out protocol, out address, out port))
                            {
                                if (protocol != WorkUtils.ProtocolLocal)
                                {
                                    partition = protocol + WorkUtils.ProtocolSeparator + address + WorkUtils.Colon + port;
                                    partition = partition.ToLower();
                                    if (!drivePartitionMappings.ContainsKey(partition))
                                        drivePartitionMappings.Add(partition, partition);
                                }
                                else
                                    partition = Path.GetPathRoot(file.FileName).ToLower();
                            }
                            else
                            {
                                //oops...
                                partition = WorkUtils.DefaultFileKey;
                            }
                            string drive;
                            if (drivePartitionMappings.ContainsKey(partition))
                                drive = drivePartitionMappings[partition];
                            else
                                drive = WorkUtils.DefaultFileKey;
                            if (!sameGroupFiles.ContainsKey(drive))
                                sameGroupFiles.Add(drive, new List<BasicFileInfo>());
                            sameGroupFiles[drive].Add(file);
                        }
                    }
                }

                SynchronizedDictionary<string, MatchFileItem> matchFiles = new SynchronizedDictionary<string, MatchFileItem>();
                List<WorkV7FindResultRunner> findResultRunners = new List<WorkV7FindResultRunner>();
                Dictionary<string, FileURI> fileAccess = new Dictionary<string, FileURI>();
                if (FilePaths != null)
                {
                    foreach (FileURI item in FilePaths)
                    {
                        string protocol;
                        string address;
                        int port;
                        if (WorkUtils.ParseProtocol(item.Uri, out protocol, out address, out port))
                        {
                            if (protocol != WorkUtils.ProtocolLocal)
                            {
                                string key = protocol + WorkUtils.ProtocolSeparator + address + WorkUtils.Colon + port;
                                key = key.ToLower();
                                if (!fileAccess.ContainsKey(key))
                                    fileAccess.Add(key, item);
                            }
                        }
                    }
                }
                foreach (string item in sameGroupFiles.Keys)
                {
                    WorkV7FindResultRunner runner = new WorkV7FindResultRunner(sameGroupFiles[item], fileAccess.ContainsKey(item) ? fileAccess[item] : null, Keyword, SearchType, MatchType, this, matchFiles);
                    findResultRunners.Add(runner);
                }
                ParallelProcessor.ExecuteParallel(findResultRunners.ToArray());
                foreach (WorkV7FindResultRunner runner in findResultRunners)
                {
                    matchCount += runner.MatchCount;
                }

                OnProgress(WorkProgressTypes.Detail, "match count:" + matchCount);

                foreach (string item in matchFiles.Keys)
                {
                    result.Add(item, matchFiles[item]);
                }
                LocalHashStorage.Instance.Flush();
                LocalFileContentIndexStorage.Instance.Flush();
                LocalNTFSStorage.Instance.Flush();
            }
            else
            {
                foreach (string key in SameFiles.Keys)
                {
                    List<BasicFileInfo> item = SameFiles[key];
                    //if (item.Count > 1 || WorkMode == WorkModes.Server)
                    {
                        MatchFileItem matchFile = new MatchFileItem();
                        matchFile.MatchFiles = item;
                        result.Add(key, matchFile);
                    }
                }
            }
            return result;
        }

        public override List<BasicFileInfo> DeleteAll(List<BasicFileInfo> Files, FileURI[] FilePaths)
        {
            SynchronizedDictionary<string, List<BasicFileInfo>> sameGroupFiles = new SynchronizedDictionary<string, List<BasicFileInfo>>();
            foreach (BasicFileInfo file in Files)
            {
                string group;
                string protocol;
                string address;
                int port;
                if (WorkUtils.ParseProtocol(file.FileName, out protocol, out address, out port))
                {
                    if (protocol != WorkUtils.ProtocolLocal)
                        group = protocol + WorkUtils.ProtocolSeparator + address + WorkUtils.Colon + port;
                    else
                        group = protocol;
                }
                else
                    group = WorkUtils.DefaultFileKey;
                group = group.ToLower();
                if (!sameGroupFiles.ContainsKey(group))
                    sameGroupFiles.Add(group, new List<BasicFileInfo>());
                sameGroupFiles[group].Add(file);
            }

            Dictionary<string, FileURI> fileAccess = new Dictionary<string, FileURI>();
            foreach (FileURI item in FilePaths)
            {
                string protocol;
                string address;
                int port;
                if (WorkUtils.ParseProtocol(item.Uri, out protocol, out address, out port))
                {
                    if (!string.IsNullOrEmpty(address))
                    {
                        string key = protocol + WorkUtils.ProtocolSeparator + address + WorkUtils.Colon + port;
                        key = key.ToLower();
                        if (!fileAccess.ContainsKey(key))
                            fileAccess.Add(key, item);
                    }
                }
            }

            SynchronizedDictionary<string, BasicFileInfo> result = new SynchronizedDictionary<string, BasicFileInfo>();
            List<WorkV7DeleteResultRunner> deleteResultRunners = new List<WorkV7DeleteResultRunner>();
            foreach (string item in sameGroupFiles.Keys)
            {
                WorkV7DeleteResultRunner runner = new WorkV7DeleteResultRunner(sameGroupFiles[item], fileAccess.ContainsKey(item) ? fileAccess[item] : null, this, result);
                deleteResultRunners.Add(runner);
            }
            ParallelProcessor.ExecuteParallel(deleteResultRunners.ToArray());
            List<BasicFileInfo> deletedFiles = new List<BasicFileInfo>();
            foreach (BasicFileInfo item in result.Values)
            {
                deletedFiles.Add(item);
            }
            return deletedFiles;
        }

        public SynchronizedDictionary<string, KeyValuePair<BasicFileInfo, BasicFileInfo>> Transfer(List<KeyValuePair<FileURI, FileURI>> FilePaths, string[] IncludesFileTypes, TransferMethods TransferMethod, SearchTypes SearchType, OverwriteModes OverwriteMode)
        {
            SynchronizedDictionary<string, KeyValuePair<BasicFileInfo, BasicFileInfo>> result = new SynchronizedDictionary<string, KeyValuePair<BasicFileInfo, BasicFileInfo>>();
            List<WorkV7TransferRunner> deleteResultRunners = new List<WorkV7TransferRunner>();
            foreach (KeyValuePair<FileURI, FileURI> item in FilePaths)
            {
                WorkV7TransferRunner runner = new WorkV7TransferRunner(item.Key, item.Value, IncludesFileTypes, TransferMethod, SearchType, OverwriteMode, this, result);
                deleteResultRunners.Add(runner);
            }
            ParallelProcessor.ExecuteParallel(deleteResultRunners.ToArray());
            return result;
        }
    }
    #endregion
}
